Exemplo n.º 1
0
def _build_objmask(img, ivar, skypix, boxcar=5, boxsize=1024):
    """Build an object mask by doing a quick estimate of the sky background on a
    given image.

    skypix - True is unmasked pixels

    """
    from scipy.ndimage.morphology import binary_dilation
    from scipy.ndimage.filters import uniform_filter
    from tractor.splinesky import SplineSky

    # Get an initial guess of the sky using the mode, otherwise the median.
    skysig1 = 1.0 / np.sqrt(np.median(ivar[skypix]))
    skyval = np.median(img[skypix])

    # Mask objects in a boxcar-smoothed (image - initial sky model), smoothed by
    # a boxcar filter before cutting pixels above the n-sigma threshold.
    if min(img.shape) / boxsize < 4:  # handle half-DECam chips
        boxsize /= 2

    # Compute initial model...
    if img.shape[0] > boxsize:
        skyobj = SplineSky.BlantonMethod(img - skyval, skypix, boxsize)
        skymod = np.zeros_like(img)
        skyobj.addTo(skymod)
    else:
        skymod = np.zeros_like(img)

    bskysig1 = skysig1 / boxcar  # sigma of boxcar-smoothed image.
    objmask = np.abs(
        uniform_filter(img - skyval - skymod, size=boxcar,
                       mode='constant')) > (3 * bskysig1)
    objmask = binary_dilation(objmask, iterations=3)

    return np.logical_and(~objmask, skypix)  # True = sky pixels
Exemplo n.º 2
0
    def get_sky_and_sigma(self, img):
        # Spline sky model to handle (?) ghost / pupil?
        from tractor.splinesky import SplineSky

        splinesky = SplineSky.BlantonMethod(img, None, 256)
        skyimg = np.zeros_like(img)
        splinesky.addTo(skyimg)

        mnsky, sig1 = sensible_sigmaclip(img - skyimg)
        return skyimg, sig1
Exemplo n.º 3
0
    def run_calibs(self,
                   psfex=True,
                   sky=True,
                   funpack=False,
                   git_version=None,
                   force=False,
                   **kwargs):
        from astrometry.util.file import trymakedirs
        from legacypipe.common import (create_temp, get_version_header,
                                       get_git_version)

        print('run_calibs for', self.name, ': sky=', sky, 'kwargs', kwargs)

        se = False
        if psfex and os.path.exists(self.psffn) and (not force):
            psfex = False
        if psfex:
            se = True

        if se and os.path.exists(self.sefn) and (not force):
            se = False
        if se:
            funpack = True

        if sky and (not force) and os.path.exists(self.splineskyfn):
            sky = False

        tmpimgfn = None
        tmpmaskfn = None

        # Unpacked image file
        funimgfn = self.imgfn
        funmaskfn = self.dqfn

        if funpack:
            # For FITS files that are not actually fpack'ed, funpack -E
            # fails.  Check whether actually fpacked.
            hdr = fitsio.read_header(self.imgfn, ext=self.hdu)
            if not ((hdr['XTENSION'] == 'BINTABLE')
                    and hdr.get('ZIMAGE', False)):
                print(
                    'Image', self.imgfn, 'HDU', self.hdu,
                    'is not actually fpacked; not funpacking, just imcopying.')
                fcopy = True

            tmpimgfn = create_temp(suffix='.fits')
            tmpmaskfn = create_temp(suffix='.fits')

            cmd = 'funpack -E %i -O %s %s' % (self.hdu, tmpimgfn, self.imgfn)
            print(cmd)
            if os.system(cmd):
                raise RuntimeError('Command failed: ' + cmd)
            funimgfn = tmpimgfn

            cmd = 'funpack -E %i -O %s %s' % (self.hdu, tmpmaskfn, self.dqfn)
            print(cmd)
            if os.system(cmd):
                print('Command failed: ' + cmd)
                M, hdr = self._read_fits(self.dqfn, ext=self.hdu, header=True)
                print('Read', M.dtype, M.shape)
                fitsio.write(tmpmaskfn, M, header=hdr, clobber=True)
                print('Wrote', tmpmaskfn, 'with fitsio')
            funmaskfn = tmpmaskfn

        if se:
            # grab header values...
            primhdr = self.read_image_primary_header()
            magzp = primhdr['MAGZERO']
            seeing = self.pixscale * self.fwhm

            print('FWHM', self.fwhm, 'pix')
            print('pixscale', self.pixscale, 'arcsec/pix')
            print('Seeing', seeing, 'arcsec')

        if se:
            maskstr = '-FLAG_IMAGE ' + funmaskfn
            sedir = self.decals.get_se_dir()

            trymakedirs(self.sefn, dir=True)

            cmd = ' '.join([
                'sex',
                '-c',
                os.path.join(sedir, 'DECaLS.se'),
                maskstr,
                '-SEEING_FWHM %f' % seeing,
                '-PARAMETERS_NAME',
                os.path.join(sedir, 'DECaLS.param'),
                '-FILTER_NAME',
                os.path.join(sedir, 'gauss_5.0_9x9.conv'),
                '-STARNNW_NAME',
                os.path.join(sedir, 'default.nnw'),
                '-PIXEL_SCALE 0',
                # SE has a *bizarre* notion of "sigma"
                '-DETECT_THRESH 1.0',
                '-ANALYSIS_THRESH 1.0',
                '-MAG_ZEROPOINT %f' % magzp,
                '-CATALOG_NAME',
                self.sefn,
                funimgfn
            ])
            print(cmd)
            if os.system(cmd):
                raise RuntimeError('Command failed: ' + cmd)
        if psfex:
            sedir = self.decals.get_se_dir()
            trymakedirs(self.psffn, dir=True)

            # If we wrote *.psf instead of *.fits in a previous run...
            oldfn = self.psffn.replace('.fits', '.psf')
            if os.path.exists(oldfn):
                print('Moving', oldfn, 'to', self.psffn)
                os.rename(oldfn, self.psffn)
            else:
                primhdr = self.read_image_primary_header()
                plver = primhdr.get('PLVER', '')
                verstr = get_git_version()
                cmds = [
                    'psfex -c %s -PSF_DIR %s %s' %
                    (os.path.join(sedir, 'DECaLS.psfex'),
                     os.path.dirname(self.psffn), self.sefn),
                    'modhead %s LEGPIPEV %s "legacypipe git version"' %
                    (self.psffn, verstr),
                    'modhead %s PLVER %s "CP ver of image file"' %
                    (self.psffn, plver)
                ]
                for cmd in cmds:
                    print(cmd)
                    rtn = os.system(cmd)
                    if rtn:
                        raise RuntimeError('Command failed: ' + cmd +
                                           ': return value: %i' % rtn)

        if sky:
            #print('Fitting sky for', self)

            hdr = get_version_header(None,
                                     self.decals.get_decals_dir(),
                                     git_version=git_version)
            primhdr = self.read_image_primary_header()
            plver = primhdr.get('PLVER', '')
            hdr.delete('PROCTYPE')
            hdr.add_record(
                dict(name='PROCTYPE',
                     value='ccd',
                     comment='NOAO processing type'))
            hdr.add_record(
                dict(name='PRODTYPE',
                     value='skymodel',
                     comment='NOAO product type'))
            hdr.add_record(
                dict(name='PLVER', value=plver,
                     comment='CP ver of image file'))

            slc = self.get_good_image_slice(None)
            #print('Good image slice is', slc)

            img = self.read_image(slice=slc)
            wt = self.read_invvar(slice=slc)

            from tractor.splinesky import SplineSky
            from scipy.ndimage.morphology import binary_dilation

            # Start by subtracting the overall median
            med = np.median(img[wt > 0])
            # Compute initial model...
            skyobj = SplineSky.BlantonMethod(img - med, wt > 0, 512)
            skymod = np.zeros_like(img)
            skyobj.addTo(skymod)
            # Now mask bright objects in (image - initial sky model)
            sig1 = 1. / np.sqrt(np.median(wt[wt > 0]))
            masked = (img - med - skymod) > (5. * sig1)
            masked = binary_dilation(masked, iterations=3)
            masked[wt == 0] = True
            # Now find the final sky model using that more extensive mask
            skyobj = SplineSky.BlantonMethod(img - med, np.logical_not(masked),
                                             512)
            # add the median back in
            skyobj.offset(med)

            if slc is not None:
                sy, sx = slc
                y0 = sy.start
                x0 = sx.start
                skyobj.shift(-x0, -y0)

            trymakedirs(self.splineskyfn, dir=True)
            skyobj.write_fits(self.splineskyfn, primhdr=hdr)
            print('Wrote sky model', self.splineskyfn)

        if tmpimgfn is not None:
            os.unlink(tmpimgfn)
        if tmpmaskfn is not None:
            os.unlink(tmpmaskfn)
Exemplo n.º 4
0
    def run_calibs(self,
                   pvastrom=True,
                   psfex=True,
                   sky=True,
                   se=False,
                   funpack=False,
                   fcopy=False,
                   use_mask=True,
                   force=False,
                   just_check=False,
                   git_version=None,
                   splinesky=False):
        '''
        Run calibration pre-processing steps.

        Parameters
        ----------
        just_check: boolean
            If True, returns True if calibs need to be run.
        '''
        from .common import (create_temp, get_version_header, get_git_version)

        #for fn in [self.pvwcsfn, self.sefn, self.psffn, self.skyfn, self.splineskyfn]:
        #    print('exists?', os.path.exists(fn), fn)

        if psfex and os.path.exists(self.psffn) and (not force):
            # Sometimes SourceExtractor gets interrupted or something and
            # writes out 0 detections.  Then PsfEx fails but in a way that
            # an output file is still written.  Try to detect & fix this
            # case.
            # Check the PsfEx output file for POLNAME1
            hdr = fitsio.read_header(self.psffn, ext=1)
            if hdr.get('POLNAME1', None) is None:
                print('Did not find POLNAME1 in PsfEx header', self.psffn,
                      '-- deleting')
                os.unlink(self.psffn)
            else:
                psfex = False
        if psfex:
            se = True

        if se and os.path.exists(self.sefn) and (not force):
            # Check SourceExtractor catalog for size = 0
            fn = self.sefn
            T = fits_table(fn, hdu=2)
            print('Read', len(T), 'sources from SE catalog', fn)
            if T is None or len(T) == 0:
                print('SourceExtractor catalog', fn,
                      'has no sources -- deleting')
                try:
                    os.unlink(fn)
                except:
                    pass
            if os.path.exists(self.sefn):
                se = False
        if se:
            funpack = True

        if pvastrom and os.path.exists(self.pvwcsfn) and (not force):
            fn = self.pvwcsfn
            if os.path.exists(fn):
                try:
                    wcs = Sip(fn)
                except:
                    print('Failed to read PV-SIP file', fn, '-- deleting')
                    os.unlink(fn)
            if os.path.exists(fn):
                pvastrom = False

        if sky and (not force) and (
            (os.path.exists(self.skyfn) and not splinesky) or
            (os.path.exists(self.splineskyfn) and splinesky)):
            fn = self.skyfn
            if splinesky:
                fn = self.splineskyfn

            if os.path.exists(fn):
                try:
                    hdr = fitsio.read_header(fn)
                except:
                    print('Failed to read sky file', fn, '-- deleting')
                    os.unlink(fn)
            if os.path.exists(fn):
                print('File', fn, 'exists -- skipping')
                sky = False

        if just_check:
            return (se or psfex or sky or pvastrom)

        tmpimgfn = None
        tmpmaskfn = None

        # Unpacked image file
        funimgfn = self.imgfn
        funmaskfn = self.dqfn

        if funpack:
            # For FITS files that are not actually fpack'ed, funpack -E
            # fails.  Check whether actually fpacked.
            hdr = fitsio.read_header(self.imgfn, ext=self.hdu)
            if not ((hdr['XTENSION'] == 'BINTABLE')
                    and hdr.get('ZIMAGE', False)):
                print(
                    'Image', self.imgfn, 'HDU', self.hdu,
                    'is not actually fpacked; not funpacking, just imcopying.')
                fcopy = True

            tmpimgfn = create_temp(suffix='.fits')
            tmpmaskfn = create_temp(suffix='.fits')

            if fcopy:
                cmd = 'imcopy %s"+%i" %s' % (self.imgfn, self.hdu, tmpimgfn)
            else:
                cmd = 'funpack -E %i -O %s %s' % (self.hdu, tmpimgfn,
                                                  self.imgfn)
            print(cmd)
            if os.system(cmd):
                raise RuntimeError('Command failed: ' + cmd)
            funimgfn = tmpimgfn

            if use_mask:
                if fcopy:
                    cmd = 'imcopy %s"+%i" %s' % (self.dqfn, self.hdu,
                                                 tmpmaskfn)
                else:
                    cmd = 'funpack -E %i -O %s %s' % (self.hdu, tmpmaskfn,
                                                      self.dqfn)
                print(cmd)
                if os.system(cmd):
                    #raise RuntimeError('Command failed: ' + cmd)
                    print('Command failed: ' + cmd)
                    M, hdr = fitsio.read(self.dqfn, ext=self.hdu, header=True)
                    print('Read', M.dtype, M.shape)
                    fitsio.write(tmpmaskfn, M, header=hdr, clobber=True)
                    print('Wrote', tmpmaskfn, 'with fitsio')

                funmaskfn = tmpmaskfn

        if se:
            # grab header values...
            primhdr = self.read_image_primary_header()
            magzp = primhdr['MAGZERO']
            seeing = self.pixscale * self.fwhm
            #print('FWHM', self.fwhm, 'pix')
            #print('pixscale', self.pixscale, 'arcsec/pix')
            #print('Seeing', seeing, 'arcsec')

        if se:
            maskstr = ''
            if use_mask:
                maskstr = '-FLAG_IMAGE ' + funmaskfn
            sedir = self.decals.get_se_dir()

            trymakedirs(self.sefn, dir=True)

            cmd = ' '.join([
                'sex',
                '-c',
                os.path.join(sedir, 'DECaLS.se'),
                maskstr,
                '-SEEING_FWHM %f' % seeing,
                '-PARAMETERS_NAME',
                os.path.join(sedir, 'DECaLS.param'),
                '-FILTER_NAME',
                os.path.join(sedir, 'gauss_5.0_9x9.conv'),
                '-STARNNW_NAME',
                os.path.join(sedir, 'default.nnw'),
                '-PIXEL_SCALE 0',
                # SE has a *bizarre* notion of "sigma"
                '-DETECT_THRESH 1.0',
                '-ANALYSIS_THRESH 1.0',
                '-MAG_ZEROPOINT %f' % magzp,
                '-CATALOG_NAME',
                self.sefn,
                funimgfn
            ])
            print(cmd)
            if os.system(cmd):
                raise RuntimeError('Command failed: ' + cmd)

        if pvastrom:
            # DECam images appear to have PV coefficients up to PVx_10,
            # which are up to cubic terms in xi,eta,r.  Overshoot what we
            # need in SIP terms.
            tmpwcsfn = create_temp(suffix='.wcs')
            cmd = ('wcs-pv2sip -S -o 6 -e %i %s %s' %
                   (self.hdu, self.imgfn, tmpwcsfn))
            print(cmd)
            if os.system(cmd):
                raise RuntimeError('Command failed: ' + cmd)
            # Read the resulting WCS header and add version info cards to it.
            version_hdr = get_version_header(None,
                                             self.decals.get_decals_dir(),
                                             git_version=git_version)
            primhdr = self.read_image_primary_header()
            plver = primhdr.get('PLVER', '').strip()
            version_hdr.add_record(
                dict(name='PLVER', value=plver,
                     comment='CP ver of image file'))
            wcshdr = fitsio.read_header(tmpwcsfn)
            os.unlink(tmpwcsfn)
            for r in wcshdr.records():
                version_hdr.add_record(r)

            trymakedirs(self.pvwcsfn, dir=True)

            fitsio.write(self.pvwcsfn, None, header=version_hdr, clobber=True)
            print('Wrote', self.pvwcsfn)

        if psfex:
            sedir = self.decals.get_se_dir()

            trymakedirs(self.psffn, dir=True)

            # If we wrote *.psf instead of *.fits in a previous run...
            oldfn = self.psffn.replace('.fits', '.psf')
            if os.path.exists(oldfn):
                print('Moving', oldfn, 'to', self.psffn)
                os.rename(oldfn, self.psffn)
            else:
                primhdr = self.read_image_primary_header()
                plver = primhdr.get('PLVER', '')
                verstr = get_git_version()
                cmds = [
                    'psfex -c %s -PSF_DIR %s %s' %
                    (os.path.join(sedir, 'DECaLS.psfex'),
                     os.path.dirname(self.psffn), self.sefn),
                    'modhead %s LEGPIPEV %s "legacypipe git version"' %
                    (self.psffn, verstr),
                    'modhead %s PLVER %s "CP ver of image file"' %
                    (self.psffn, plver)
                ]
                for cmd in cmds:
                    print(cmd)
                    rtn = os.system(cmd)
                    if rtn:
                        raise RuntimeError('Command failed: ' + cmd +
                                           ': return value: %i' % rtn)

        if sky:
            #print('Fitting sky for', self)

            hdr = get_version_header(None,
                                     self.decals.get_decals_dir(),
                                     git_version=git_version)
            primhdr = self.read_image_primary_header()
            plver = primhdr.get('PLVER', '')
            hdr.delete('PROCTYPE')
            hdr.add_record(
                dict(name='PROCTYPE',
                     value='ccd',
                     comment='NOAO processing type'))
            hdr.add_record(
                dict(name='PRODTYPE',
                     value='skymodel',
                     comment='NOAO product type'))
            hdr.add_record(
                dict(name='PLVER', value=plver,
                     comment='CP ver of image file'))

            slc = self.get_good_image_slice(None)
            #print('Good image slice is', slc)

            img = self.read_image(slice=slc)
            wt = self.read_invvar(slice=slc)

            if splinesky:
                from tractor.splinesky import SplineSky
                from scipy.ndimage.morphology import binary_dilation

                # Start by subtracting the overall median
                med = np.median(img[wt > 0])
                # Compute initial model...
                skyobj = SplineSky.BlantonMethod(img - med, wt > 0, 512)
                skymod = np.zeros_like(img)
                skyobj.addTo(skymod)
                # Now mask bright objects in (image - initial sky model)
                sig1 = 1. / np.sqrt(np.median(wt[wt > 0]))
                masked = (img - med - skymod) > (5. * sig1)
                masked = binary_dilation(masked, iterations=3)
                masked[wt == 0] = True
                # Now find the final sky model using that more extensive mask
                skyobj = SplineSky.BlantonMethod(img - med,
                                                 np.logical_not(masked), 512)
                # add the median back in
                skyobj.offset(med)

                if slc is not None:
                    sy, sx = slc
                    y0 = sy.start
                    x0 = sx.start
                    skyobj.shift(-x0, -y0)

                trymakedirs(self.splineskyfn, dir=True)
                skyobj.write_fits(self.splineskyfn, primhdr=hdr)
                print('Wrote sky model', self.splineskyfn)

            else:
                img = img[wt > 0]
                try:
                    skyval = estimate_mode(img, raiseOnWarn=True)
                    skymeth = 'mode'
                except:
                    skyval = np.median(img)
                    skymeth = 'median'
                tsky = ConstantSky(skyval)

                hdr.add_record(
                    dict(name='SKYMETH',
                         value=skymeth,
                         comment='estimate_mode, or fallback to median?'))
                trymakedirs(self.skyfn, dir=True)
                tsky.write_fits(self.skyfn, hdr=hdr)
                print('Wrote sky model', self.skyfn)

        if tmpimgfn is not None:
            os.unlink(tmpimgfn)
        if tmpmaskfn is not None:
            os.unlink(tmpmaskfn)
Exemplo n.º 5
0
def main():
    #img = fitsio.read('347736-S5.fits')
    img = fitsio.read('392772-N29.fits')
    print(img.shape)
    img = img.T.copy()
    mm = np.median(img)
    img -= mm

    ps = PlotSequence('sky')
    lo, hi = np.percentile(img, [20, 80])
    ima = dict(vmin=lo,
               vmax=hi,
               interpolation='nearest',
               origin='lower',
               cmap='gray')
    plt.clf()
    plt.imshow(img, **ima)
    ps.savefig()

    # PAD
    padimg = np.zeros((2048, 4096))
    padimg[1:-1, 1:-1] = img
    img = padimg

    from tractor.splinesky import SplineSky
    from scipy.ndimage.morphology import binary_dilation
    from astrometry.util.util import median_smooth

    # # 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)

    mask = np.zeros(img.shape, bool)
    mask[binary_dilation(img > 5 * sig1, iterations=5)] = True

    for mm in [None, mask]:

        notmm = None
        if mm is not None:
            notmm = np.logical_not(mm)

        sky = SplineSky.BlantonMethod(img, notmm, 512)

        skyfn = 'sky-%s.fits' % (mm is not None and 'mask' or 'nomask')
        sky.write_fits(skyfn)

        from tractor.utils import get_class_from_name

        print('Reading sky model from', skyfn)
        hdr = fitsio.read_header(skyfn)
        skyclass = hdr['SKY']
        clazz = get_class_from_name(skyclass)

        if getattr(clazz, 'from_fits'):
            fromfits = getattr(clazz, 'from_fits')
            skyobj = fromfits(skyfn, hdr)
        else:
            fromfits = getattr(clazz, 'fromFitsHeader')
            skyobj = fromfits(hdr, prefix='SKY_')
        sky2 = skyobj
        print('sky2', sky2)
        sky2.write_fits(skyfn.replace('sky', 'sky2'))

        mod = np.zeros_like(img)
        sky.addTo(mod)

        plt.clf()
        plt.imshow(mod, **ima)
        plt.title('Blanton method')
        ps.savefig()

        plt.clf()
        plt.imshow(img - mod, **ima)
        plt.title('Blanton method (subtracted)')
        ps.savefig()

        grid = 512
        #grid = 256
        img = img.astype(np.float32)
        med = np.zeros_like(img)
        median_smooth(img, mm, grid / 2, med)

        plt.clf()
        plt.imshow(med, **ima)
        plt.title('dmedsmooth')
        ps.savefig()

        plt.clf()
        plt.imshow(img - med, **ima)
        plt.title('dmedsmooth (subtracted)')
        ps.savefig()

    sys.exit(0)

    med2 = np.zeros_like(img)
    mask = np.zeros(img.shape, bool)
    mask[binary_dilation(img > 5 * sig1, iterations=5)] = True
    median_smooth(img, mask, grid / 2, med2)

    # UN-PAD
    img = img[1:-1, 1:-1]
    med = med[1:-1, 1:-1]
    med2 = med2[1:-1, 1:-1]
    mask = mask[1:-1, 1:-1]

    sub = img - med

    plt.clf()
    plt.imshow(sub, **ima)
    ps.savefig()

    plt.clf()
    plt.imshow(img - med2, **ima)
    ps.savefig()

    plt.clf()
    plt.imshow(img * (1 - mask), **ima)
    ps.savefig()

    plt.clf()
    plt.imshow(med2, **ima)
    ps.savefig()

    lo2, hi2 = np.percentile(img, [5, 95])

    ha = dict(bins=100, range=(lo2, hi2), log=True, histtype='step')
    plt.clf()
    n1, b, p = plt.hist(img.ravel(), color='r', **ha)
    n2, b, p = plt.hist(sub.ravel(), color='b', **ha)
    mx = max(max(n1), max(n2))
    plt.ylim(mx * 0.1, mx)
    ps.savefig()

    ha = dict(bins=100, range=(lo2, hi2), histtype='step')
    plt.clf()
    n1, b, p = plt.hist(img.ravel(), color='r', **ha)
    n2, b, p = plt.hist(sub.ravel(), color='b', **ha)

    n3, b, p = plt.hist((img - sub).ravel(), color='m', **ha)

    #mx = max(max(n1), max(n2))
    #plt.ylim(0, mx)
    ps.savefig()
Exemplo n.º 6
0
    def run_calibs(self, psfex=True, sky=True, se=False,
                   funpack=False, fcopy=False, use_mask=True,
                   force=False, just_check=False, git_version=None,
                   splinesky=False):

        '''
        Run calibration pre-processing steps.

        Parameters
        ----------
        just_check: boolean
            If True, returns True if calibs need to be run.
        '''
        from .survey import (create_temp, get_version_header,
                             get_git_version)
        
        if psfex and os.path.exists(self.psffn) and (not force):
            if self.check_psf(self.psffn):
                psfex = False
        if psfex:
            se = True
            
        if se and os.path.exists(self.sefn) and (not force):
            if self.check_se_cat(self.sefn):
                se = False
        if se:
            funpack = True

        if sky and (not force) and (
            (os.path.exists(self.skyfn) and not splinesky) or
            (os.path.exists(self.splineskyfn) and splinesky)):
            fn = self.skyfn
            if splinesky:
                fn = self.splineskyfn

            if os.path.exists(fn):
                try:
                    hdr = fitsio.read_header(fn)
                except:
                    print('Failed to read sky file', fn, '-- deleting')
                    os.unlink(fn)
            if os.path.exists(fn):
                print('File', fn, 'exists -- skipping')
                sky = False

        if just_check:
            return (se or psfex or sky)

        todelete = []
        if funpack:
            # The image & mask files to process (funpacked if necessary)
            imgfn,maskfn = self.funpack_files(self.imgfn, self.dqfn, self.hdu, todelete)
        else:
            imgfn,maskfn = self.imgfn,self.dqfn
    
        if se:
            self.run_se('DECaLS', imgfn, maskfn)
        if psfex:
            self.run_psfex('DECaLS')

        if sky:
            #print('Fitting sky for', self)

            hdr = get_version_header(None, self.survey.get_survey_dir(),
                                     git_version=git_version)
            primhdr = self.read_image_primary_header()
            plver = primhdr.get('PLVER', '')
            hdr.delete('PROCTYPE')
            hdr.add_record(dict(name='PROCTYPE', value='ccd',
                                comment='NOAO processing type'))
            hdr.add_record(dict(name='PRODTYPE', value='skymodel',
                                comment='NOAO product type'))
            hdr.add_record(dict(name='PLVER', value=plver,
                                comment='CP ver of image file'))

            slc = self.get_good_image_slice(None)
            #print('Good image slice is', slc)

            img = self.read_image(slice=slc)
            wt = self.read_invvar(slice=slc)

            if splinesky:
                from tractor.splinesky import SplineSky
                from scipy.ndimage.morphology import binary_dilation

                boxsize = DecamImage.splinesky_boxsize
                
                # Start by subtracting the overall median
                med = np.median(img[wt>0])
                # Compute initial model...
                skyobj = SplineSky.BlantonMethod(img - med, wt>0, boxsize)
                skymod = np.zeros_like(img)
                skyobj.addTo(skymod)
                # Now mask bright objects in (image - initial sky model)
                sig1 = 1./np.sqrt(np.median(wt[wt>0]))
                masked = (img - med - skymod) > (5.*sig1)
                masked = binary_dilation(masked, iterations=3)
                masked[wt == 0] = True

                sig1b = 1./np.sqrt(np.median(wt[masked == False]))
                print('Sig1 vs sig1b:', sig1, sig1b)

                # Now find the final sky model using that more extensive mask
                skyobj = SplineSky.BlantonMethod(
                    img - med, np.logical_not(masked), boxsize)
                # add the overall median back in
                skyobj.offset(med)

                if slc is not None:
                    sy,sx = slc
                    y0 = sy.start
                    x0 = sx.start
                    skyobj.shift(-x0, -y0)

                hdr.add_record(dict(name='SIG1', value=sig1,
                                    comment='Median stdev of unmasked pixels'))
                hdr.add_record(dict(name='SIG1B', value=sig1,
                                    comment='Median stdev of unmasked pixels+'))
                    
                trymakedirs(self.splineskyfn, dir=True)
                skyobj.write_fits(self.splineskyfn, primhdr=hdr)
                print('Wrote sky model', self.splineskyfn)
    
            else:
                try:
                    skyval = estimate_mode(img[wt > 0], raiseOnWarn=True)
                    skymeth = 'mode'
                except:
                    skyval = np.median(img[wt > 0])
                    skymeth = 'median'
                tsky = ConstantSky(skyval)

                hdr.add_record(dict(name='SKYMETH', value=skymeth,
                                    comment='estimate_mode, or fallback to median?'))

                from scipy.ndimage.morphology import binary_dilation
                sig1 = 1./np.sqrt(np.median(wt[wt>0]))
                masked = (img - skyval) > (5.*sig1)
                masked = binary_dilation(masked, iterations=3)
                masked[wt == 0] = True
                sig1b = 1./np.sqrt(np.median(wt[masked == False]))
                print('Sig1 vs sig1b:', sig1, sig1b)

                hdr.add_record(dict(name='SIG1', value=sig1,
                                    comment='Median stdev of unmasked pixels'))
                hdr.add_record(dict(name='SIG1B', value=sig1,
                                    comment='Median stdev of unmasked pixels+'))
                
                trymakedirs(self.skyfn, dir=True)
                tsky.write_fits(self.skyfn, hdr=hdr)
                print('Wrote sky model', self.skyfn)

        for fn in todelete:
            os.unlink(fn)