예제 #1
0
파일: psf.py 프로젝트: astroweaver/tractor
    def getPointSourcePatch(self,
                            px,
                            py,
                            minval=0.,
                            modelMask=None,
                            radius=None,
                            **kwargs):
        from astrometry.util.miscutils import get_overlapping_region

        img = self.getImage(px, py)
        assert (np.all(np.isfinite(img)))

        if radius is not None:
            R = int(np.ceil(radius))
            H, W = img.shape
            cx = W // 2
            cy = H // 2
            img = img[max(cy - R, 0):min(cy + R + 1, H - 1),
                      max(cx - R, 0):min(cx + R + 1, W - 1)]

        H, W = img.shape
        ix = int(np.round(px))
        iy = int(np.round(py))
        dx = px - ix
        dy = py - iy
        x0 = ix - W // 2
        y0 = iy - H // 2

        if modelMask is None:
            outimg = lanczos_shift_image(img, dx, dy)
            return Patch(x0, y0, outimg)

        mh, mw = modelMask.shape
        mx0, my0 = modelMask.x0, modelMask.y0

        # print 'PixelizedPSF + modelMask'
        # print 'mx0,my0', mx0,my0, '+ mw,mh', mw,mh
        # print 'PSF image x0,y0', x0,y0, '+ W,H', W,H

        if ((mx0 >= x0 + W) or (mx0 + mw <= x0) or (my0 >= y0 + H)
                or (my0 + mh <= y0)):
            # No overlap
            return None
        # Otherwise, we'll just produce the Lanczos-shifted PSF
        # image as usual, and then copy it into the modelMask
        # space.
        L = 3
        padding = L
        # Create a modelMask + padding sized stamp and insert PSF image into it
        mm = np.zeros((mh + 2 * padding, mw + 2 * padding), np.float32)
        yi, yo = get_overlapping_region(my0 - y0 - padding,
                                        my0 - y0 + mh - 1 + padding, 0, H - 1)
        xi, xo = get_overlapping_region(mx0 - x0 - padding,
                                        mx0 - x0 + mw - 1 + padding, 0, W - 1)
        mm[yo, xo] = img[yi, xi]
        mm = lanczos_shift_image(mm, dx, dy)
        mm = mm[padding:-padding, padding:-padding]
        assert (np.all(np.isfinite(mm)))

        return Patch(mx0, my0, mm)
예제 #2
0
파일: engine.py 프로젝트: ziyaointl/tractor
    def getDerivs(self):
        '''
        Computes model-image derivatives for each parameter.

        Returns a nested list of tuples:

        allderivs: [
           (param0:)  [  (deriv, img), (deriv, img), ... ],
           (param1:)  [],
           (param2:)  [  (deriv, img), ],
        ]

        Where the *derivs* are *Patch* objects and *imgs* are *Image*
        objects.
        '''
        allderivs = []

        if self.isParamFrozen('catalog'):
            srcs = []
        else:
            srcs = list(self.catalog.getThawedSources())

        allsrcs = self.catalog

        if not self.isParamFrozen('images'):
            for i in self.images.getThawedParamIndices():
                img = self.images[i]
                derivs = img.getParamDerivatives(self, allsrcs)
                mod0 = None
                for di, deriv in enumerate(derivs):
                    if deriv is False:
                        if mod0 is None:
                            mod0 = self.getModelImage(img)
                            p0 = img.getParams()
                            stepsizes = img.getStepSizes()
                            paramnames = img.getParamNames()
                        oldval = img.setParam(di, p0[di] + stepsizes[di])
                        mod = self.getModelImage(img)
                        img.setParam(di, oldval)
                        deriv = Patch(0, 0, (mod - mod0) / stepsizes[di])
                        deriv.name = 'd(im%i)/d(%s)' % (i, paramnames[di])
                    allderivs.append([(deriv, img)])
                del mod0

        for src in srcs:
            srcderivs = [[] for i in range(src.numberOfParams())]
            for img in self.images:
                derivs = self._getSourceDerivatives(src, img)
                for k, deriv in enumerate(derivs):
                    if deriv is None:
                        continue
                    srcderivs[k].append((deriv, img))
            allderivs.extend(srcderivs)
        #print('allderivs:', len(allderivs))
        #print('N params:', self.numberOfParams())

        assert (len(allderivs) == self.numberOfParams())
        return allderivs
예제 #3
0
    def __init__(self, real, brights, tim, ps):
        from tractor.patch import Patch
        '''
        *real*: The real source whose derivatives are my profiles.
        '''
        # This a subclass of MultiParams and we pass the brightnesses
        # as our params.
        super(SourceDerivatives,self).__init__(*brights)
        self.real = real
        self.brights = brights
        self.umods = None

        # Get the current source profile and take pixel-space
        # derivatives by hand, for speed and to get symmetric
        # derivatives.
        realmod = real.getUnitFluxModelPatch(tim)
        p = realmod.patch
        dx = np.zeros_like(p)
        dy = np.zeros_like(p)
        # Omit a boundary of 1 pixel on all sides in both derivatives
        dx[1:-1, 1:-1] = (p[1:-1, :-2] - p[1:-1, 2:]) / 2.
        dy[1:-1, 1:-1] = (p[:-2, 1:-1] - p[2:, 1:-1]) / 2.
        # Convert from pixel-space to RA,Dec derivatives via CD matrix
        px, py = tim.getWcs().positionToPixel(real.pos, real)
        cdi = tim.getWcs().cdInverseAtPixel(px, py)
        dra  = dx * cdi[0, 0] + dy * cdi[1, 0]
        ddec = dx * cdi[0, 1] + dy * cdi[1, 1]

        if ps is not None:
            import pylab as plt
            mx = p.max()
            plt.clf()
            plt.subplot(2,3,1)
            plt.imshow(p, interpolation='nearest', origin='lower',
                       vmin=0, vmax=mx)
            mx *= 0.25
            plt.subplot(2,3,2)
            plt.imshow(dx, interpolation='nearest', origin='lower',
                       vmin=-mx, vmax=mx)
            plt.title('dx')
            plt.subplot(2,3,3)
            plt.imshow(dy, interpolation='nearest', origin='lower',
                       vmin=-mx, vmax=mx)
            plt.title('dy')
            plt.subplot(2,3,5)
            sc = 3600/0.262
            plt.imshow(dra/sc, interpolation='nearest', origin='lower',
                       vmin=-mx, vmax=mx)
            plt.title('dra')
            plt.subplot(2,3,6)
            plt.imshow(ddec/sc, interpolation='nearest', origin='lower',
                       vmin=-mx, vmax=mx)
            plt.title('ddec')
            ps.savefig()
        # These are our "unit-flux" models
        self.umods = [Patch(realmod.x0, realmod.y0, dra),
                      Patch(realmod.x0, realmod.y0, ddec)]
예제 #4
0
파일: engine.py 프로젝트: ziyaointl/tractor
    def _checkModelMask(self, patch, mask):
        if self.expectModelMasks:
            if patch is not None:
                assert (mask is not None)

        if patch is not None and mask is not None:
            # not strictly required?  but a good idea!
            assert (patch.patch.shape == mask.patch.shape)

        if patch is not None and mask is not None and patch.patch is not None:
            nonzero = Patch(patch.x0, patch.y0, patch.patch != 0)
            #print('nonzero type:', nonzero.patch.dtype)
            unmasked = Patch(mask.x0, mask.y0, np.logical_not(mask.mask))
            #print('unmasked type:', unmasked.patch.dtype)
            bad = nonzero.performArithmetic(unmasked, '__iand__', otype=bool)
            assert (np.all(bad.patch == False))
예제 #5
0
    def evaluate_grid_masked(self, x0, y0, mask, fx, fy,
                             derivs=False):
        '''
        mask: np array of booleans (NOT Patch object!)
        '''
        from tractor.mix import c_gauss_2d_masked

        h, w = mask.shape
        result = np.zeros((h, w), np.float32)
        xderiv = yderiv = None
        if derivs:
            xderiv = np.zeros_like(result)
            yderiv = np.zeros_like(result)

        # print('gauss_2d_masked:', int(x0), int(y0), int(w), int (h), float(fx), float(fy),)
        # print('  ', self.amp.astype(np.float32),)
        # print('  ', self.mean.astype(np.float32),)
        # print('  ', self.var.astype(np.float32),)
        # print('  ', 'res', (result.shape, result.dtype),)
        # if xderiv is not None:
        #     print('  ', 'xd', (xderiv.shape, xderiv.dtype),)
        # if yderiv is not None:
        #     print('  ', 'yd', (yderiv.shape, yderiv.dtype),)
        # print('  ', 'mask', mask.shape, mask.dtype)

        rtn = c_gauss_2d_masked(int(x0), int(y0), int(w), int(h),
                                float(fx), float(fy),
                                self.amp.astype(np.float32),
                                self.mean.astype(np.float32),
                                self.var.astype(np.float32),
                                result, xderiv, yderiv, mask)

        # print('gauss_2d_masked returned.')

        assert(rtn == 0)
        if derivs:
            return (Patch(x0, y0, result), Patch(x0, y0, xderiv),
                    Patch(x0, y0, yderiv))
        return Patch(x0, y0, result)
예제 #6
0
 def evaluate_grid_dstn(self, x0, x1, y0, y1, cx, cy):
     '''
     [x0,x1): (int) X values to evaluate
     [y0,y1): (int) Y values to evaluate
     (cx,cy): (float) pixel center of the MoG
     '''
     from tractor.mix import c_gauss_2d_grid
     assert(self.D == 2)
     result = np.zeros((y1 - y0, x1 - x0))
     rtn = c_gauss_2d_grid(int(x0), int(x1), int(y0), int(y1), cx, cy,
                           self.amp, self.mean, self.var, result)
     if rtn == -1:
         raise RuntimeError('c_gauss_2d_grid failed')
     return Patch(x0, y0, result)
예제 #7
0
 def galaxy_norm(self, tim, x=None, y=None):
     # Galaxy-detection norm
     from tractor.galaxy import ExpGalaxy
     from tractor.ellipses import EllipseE
     from tractor.patch import Patch
     h, w = tim.shape
     band = tim.band
     if x is None:
         x = w / 2.
     if y is None:
         y = h / 2.
     pos = tim.wcs.pixelToPosition(x, y)
     gal = ExpGalaxy(pos, NanoMaggies(**{band: 1.}), EllipseE(0.45, 0., 0.))
     S = 32
     mm = Patch(int(x - S), int(y - S), np.ones((2 * S + 1, 2 * S + 1),
                                                bool))
     galmod = gal.getModelPatch(tim, modelMask=mm).patch
     galmod = np.maximum(0, galmod)
     galmod /= galmod.sum()
     galnorm = np.sqrt(np.sum(galmod**2))
     return galnorm
예제 #8
0
    def _realGetUnitFluxModelPatch(self, img, px, py, minval, modelMask=None):
        if modelMask is not None:
            x0, y0 = modelMask.x0, modelMask.y0
        else:
            # choose the patch size
            halfsize = self._getUnitFluxPatchSize(img,
                                                  px=px,
                                                  py=py,
                                                  minval=minval)
            # find overlapping pixels to render
            (outx,
             inx) = get_overlapping_region(int(np.floor(px - halfsize)),
                                           int(np.ceil(px + halfsize + 1)), 0,
                                           img.getWidth())
            (outy,
             iny) = get_overlapping_region(int(np.floor(py - halfsize)),
                                           int(np.ceil(py + halfsize + 1)), 0,
                                           img.getHeight())
            if inx == [] or iny == []:
                # no overlap
                return None
            x0, x1 = outx.start, outx.stop
            y0, y1 = outy.start, outy.stop

        psf = img.getPsf()

        # We have two methods of rendering profile galaxies: If the
        # PSF can be represented as a mixture of Gaussians, then we do
        # the analytic Gaussian convolution, producing a larger
        # mixture of Gaussians, and we render that.  Otherwise
        # (pixelized PSFs), we FFT the PSF, multiply by the analytic
        # FFT of the galaxy, and IFFT back to get the rendered
        # profile.

        # The "HybridPSF" class is just a marker to indicate whether this
        # code should treat the PSF as a hybrid.
        from tractor.psf import HybridPSF
        hybrid = isinstance(psf, HybridPSF)

        def run_mog(amix=None, mm=None):
            ''' This runs the mixture-of-Gaussians convolution method.
            '''
            if amix is None:
                amix = self._getAffineProfile(img, px, py)
            if mm is None:
                mm = modelMask
            # now convolve with the PSF, analytically
            # (note that the psf's center is *not* set to px,py; that's just
            #  the position to use for spatially-varying PSFs)
            psfmix = psf.getMixtureOfGaussians(px=px, py=py)
            cmix = amix.convolve(psfmix)
            if mm is None:
                #print('Mixture to patch: amix', amix, 'psfmix', psfmix, 'cmix', cmix)
                return mp.mixture_to_patch(cmix, x0, x1, y0, y1, minval)
            # The convolved mixture *already* has the px,py offset added
            # (via px,py to amix) so set px,py=0,0 in this call.
            if mm.mask is not None:
                p = cmix.evaluate_grid_masked(mm.x0, mm.y0, mm.mask, 0., 0.)
            else:
                p = cmix.evaluate_grid(mm.x0, mm.x1, mm.y0, mm.y1, 0., 0.)
            assert (p.shape == mm.shape)
            return p

        if hasattr(psf, 'getMixtureOfGaussians') and not hybrid:
            return run_mog(mm=modelMask)

        # Otherwise, FFT:
        imh, imw = img.shape
        if modelMask is None:
            # Avoid huge galaxies -> huge halfsize in a tiny image (blob)
            imsz = max(imh, imw)
            halfsize = min(halfsize, imsz)
            # FIXME -- should take some kind of combination of
            # modelMask, PSF, and Galaxy sizes!

        else:
            # ModelMask sets the sizes.
            mh, mw = modelMask.shape
            x1 = x0 + mw
            y1 = y0 + mh

            halfsize = max(mh / 2., mw / 2.)
            # How far from the source center to furthest modelMask edge?
            # FIXME -- add 1 for Lanczos margin?
            halfsize = max(
                halfsize,
                max(max(1 + px - x0, 1 + x1 - px), max(1 + py - y0,
                                                       1 + y1 - py)))
            psfh, psfw = psf.shape
            halfsize = max(halfsize, max(psfw / 2., psfh / 2.))
            #print('Halfsize:', halfsize)

            # is the source center outside the modelMask?
            sourceOut = (px < x0 or px > x1 - 1 or py < y0 or py > y1 - 1)
            # print('mh,mw', mh,mw, 'sourceout?', sourceOut)

            if sourceOut:
                if hybrid:
                    return run_mog(mm=modelMask)

                # Super Yuck -- FFT, modelMask, source is outside the
                # box.
                neardx, neardy = 0., 0.
                if px < x0:
                    neardx = x0 - px
                if px > x1:
                    neardx = px - x1
                if py < y0:
                    neardy = y0 - py
                if py > y1:
                    neardy = py - y1
                nearest = np.hypot(neardx, neardy)
                #print('Nearest corner:', nearest, 'vs radius', self.getRadius())
                if nearest > self.getRadius():
                    return None
                # how far is the furthest point from the source center?
                farw = max(abs(x0 - px), abs(x1 - px))
                farh = max(abs(y0 - py), abs(y1 - py))
                bigx0 = int(np.floor(px - farw))
                bigx1 = int(np.ceil(px + farw))
                bigy0 = int(np.floor(py - farh))
                bigy1 = int(np.ceil(py + farh))
                bigw = 1 + bigx1 - bigx0
                bigh = 1 + bigy1 - bigy0
                boffx = x0 - bigx0
                boffy = y0 - bigy0
                assert (bigw >= mw)
                assert (bigh >= mh)
                assert (boffx >= 0)
                assert (boffy >= 0)
                bigMask = np.zeros((bigh, bigw), bool)
                if modelMask.mask is not None:
                    bigMask[boffy:boffy + mh,
                            boffx:boffx + mw] = modelMask.mask
                else:
                    bigMask[boffy:boffy + mh, boffx:boffx + mw] = True
                bigMask = ModelMask(bigx0, bigy0, bigMask)
                # print('Recursing:', self, ':', (mh,mw), 'to', (bigh,bigw))
                bigmodel = self._realGetUnitFluxModelPatch(img,
                                                           px,
                                                           py,
                                                           minval,
                                                           modelMask=bigMask)
                return Patch(
                    x0, y0, bigmodel.patch[boffy:boffy + mh, boffx:boffx + mw])

        # print('Getting Fourier transform of PSF at', px,py)
        # print('Tim shape:', img.shape)
        P, (cx, cy), (pH, pW), (v,
                                w) = psf.getFourierTransform(px, py, halfsize)

        dx = px - cx
        dy = py - cy
        if modelMask is not None:
            # the Patch we return *must* have this origin.
            ix0 = x0
            iy0 = y0
            # the difference that we have to handle by shifting the model image
            mux = dx - ix0
            muy = dy - iy0
            # we will handle the integer portion by computing a shifted image
            # and copying it into the result
            sx = int(np.round(mux))
            sy = int(np.round(muy))
            # the subpixel portion will be handled with a Lanczos interpolation
            mux -= sx
            muy -= sy
        else:
            # Put the integer portion of the offset into Patch x0,y0
            ix0 = int(np.round(dx))
            iy0 = int(np.round(dy))
            # the subpixel portion will be handled with a Lanczos interpolation
            mux = dx - ix0
            muy = dy - iy0

        # At this point, mux,muy are both in [-0.5, 0.5]
        assert (np.abs(mux) <= 0.5)
        assert (np.abs(muy) <= 0.5)

        amix = self._getShearedProfile(img, px, py)
        fftmix = amix
        mogmix = None

        #print('Galaxy affine profile:', amix)

        if hybrid:
            # Split "amix" into terms that we will evaluate using MoG
            # vs FFT.
            vv = amix.var[:, 0, 0] + amix.var[:, 1, 1]
            nsigma = 3.
            # Terms that will wrap-around significantly...
            I = ((vv * nsigma**2) > (pW**2))
            if np.any(I):
                # print('Evaluating', np.sum(I), 'terms as MoGs')
                mogmix = mp.MixtureOfGaussians(
                    amix.amp[I],
                    amix.mean[I, :] + np.array([px, py])[np.newaxis, :],
                    amix.var[I, :, :],
                    quick=True)
            I = np.logical_not(I)
            if np.any(I):
                # print('Evaluating', np.sum(I), 'terms with FFT')
                fftmix = mp.MixtureOfGaussians(amix.amp[I],
                                               amix.mean[I, :],
                                               amix.var[I, :, :],
                                               quick=True)
            else:
                fftmix = None

        if fftmix is not None:
            Fsum = fftmix.getFourierTransform(v, w, zero_mean=True)
            # In Intel's mkl_fft library, the irfftn code path is faster than irfft2
            # (the irfft2 version sets args (to their default values) which triggers padding
            #  behavior, changing the FFT size and copying behavior)
            #G = np.fft.irfft2(Fsum * P, s=(pH, pW))
            G = np.fft.irfftn(Fsum * P)

            assert (G.shape == (pH, pW))
            # FIXME -- we could try to be sneaky and Lanczos-interp
            # after cutting G down to nearly its final size... tricky
            # tho

            # Lanczos-3 interpolation in ~the same way we do for
            # pixelized PSFs.
            from tractor.psf import lanczos_shift_image
            G = G.astype(np.float32)
            lanczos_shift_image(G, mux, muy, inplace=True)
        else:
            G = np.zeros((pH, pW), np.float32)

        if modelMask is not None:
            gh, gw = G.shape
            if sx != 0 or sy != 0:
                yi, yo = get_overlapping_region(-sy, -sy + mh - 1, 0, gh - 1)
                xi, xo = get_overlapping_region(-sx, -sx + mw - 1, 0, gw - 1)
                # shifted
                # FIXME -- are yo,xo always the whole image?  If so, optimize
                shG = np.zeros((mh, mw), G.dtype)
                shG[yo, xo] = G[yi, xi]

                if debug_ps is not None:
                    _fourier_galaxy_debug_plots(G, shG, xi, yi, xo, yo, P,
                                                Fsum, pW, pH, psf)

                G = shG
            if gh > mh or gw > mw:
                G = G[:mh, :mw]
            assert (G.shape == modelMask.shape)

        else:
            # Clip down to suggested "halfsize"
            if x0 > ix0:
                G = G[:, x0 - ix0:]
                ix0 = x0
            if y0 > iy0:
                G = G[y0 - iy0:, :]
                iy0 = y0
            gh, gw = G.shape
            if gw + ix0 > x1:
                G = G[:, :x1 - ix0]
            if gh + iy0 > y1:
                G = G[:y1 - iy0, :]

        if mogmix is not None:
            if modelMask is not None:
                mogpatch = run_mog(amix=mogmix, mm=modelMask)
            else:
                gh, gw = G.shape
                mogpatch = run_mog(amix=mogmix, mm=ModelMask(ix0, iy0, gw, gh))
            assert (mogpatch.patch.shape == G.shape)
            G += mogpatch.patch

        return Patch(ix0, iy0, G)
예제 #9
0
    def evaluate_grid_approx3(self, x0, x1, y0, y1, fx, fy, minval,
                              derivs=False, minradius=3, doslice=True,
                              maxmargin=100):
        '''
        minval: small value at which to stop evaluating

        [x0,x1): (int) X values to evaluate
        [y0,y1): (int) Y values to evaluate
        (fx,fy): (float) pixel offset of the MoG; ie, evaluate MoG shifted by
                this amount.

        'maxmargin': don't render sources more than this distance outside the box.

        If 'doslice' is True, slices the images down to the non-zero
        bounding-box.

        If 'derivs' is True, computes and returns x and y derivatives too.

        Unlike evaluate_grid_approx, returns a Patch object.
        '''
        from tractor.mix import c_gauss_2d_approx3

        result = np.zeros((y1 - y0, x1 - x0))
        xderiv = yderiv = None
        if derivs:
            xderiv = np.zeros_like(result)
            yderiv = np.zeros_like(result)

        # guess:
        cx = int(self.mean[0, 0] + fx)
        cy = int(self.mean[0, 1] + fy)

        if (cx < x0 - maxmargin or cx > x1 + maxmargin or
                cy < y0 - maxmargin or cy > y1 + maxmargin):
            return None

        try:
            rtn, sx0, sx1, sy0, sy1 = c_gauss_2d_approx3(
                int(x0), int(x1), int(y0), int(y1),
                float(fx), float(fy), float(minval),
                self.amp, self.mean, self.var,
                result, xderiv, yderiv,
                cx, cy, int(minradius))
        except:
            print('failure calling c_gauss_2d_approx3:')
            print(x0, x1, y0, y1)
            print(fx, fy, minval)
            print(cx, cy, minradius)
            print('-->', int(x0), int(x1), int(y0), int(y1))
            print('-->', float(fx), float(fy), float(minval))
            print('-->', cx, cy, int(minradius))
            raise
        assert(rtn == 0)
        if doslice:
            slc = slice(sy0, sy1), slice(sx0, sx1)
            result = result[slc].copy()
            if derivs:
                xderiv = xderiv[slc].copy()
                yderiv = yderiv[slc].copy()
            x0 += sx0
            y0 += sy0

        if derivs:
            return (Patch(x0, y0, result), Patch(x0, y0, xderiv),
                    Patch(x0, y0, yderiv))

        return Patch(x0, y0, result)
예제 #10
0
 def getParamDerivatives(self, tractor, img, srcs):
     import numpy as np
     p = Patch(0, 0, np.ones_like(img.getImage()))
     p.setName('dsky')
     return [p]