Exemple #1
0
def get_galnorm(seeing, pixsc):
    from tractor.patch import ModelMask
    S = 32
    W,H = S*2+1, S*2+1
    psf_sigma = seeing / 2.35 / pixsc
    tim = tractor.Image(data=np.zeros((H,W)), inverr=np.ones((H,W)),
                        psf=tractor.NCircularGaussianPSF([psf_sigma], [1.]),
                        wcs=tractor.NullWCS(pixscale=pixsc))
    gal = SimpleGalaxy(tractor.PixPos(S,S), tractor.Flux(1.))
    mm = ModelMask(0, 0, W, H)
    galmod = gal.getModelPatch(tim, modelMask=mm).patch
    galmod = np.maximum(0, galmod)
    galmod /= galmod.sum()
    return np.sqrt(np.sum(galmod**2))
Exemple #2
0
 def test_tiny_image(self):
     from tractor.patch import ModelMask
     H, W = 1, 1
     tim = Image(data=np.zeros((H, W)),
                 invvar=np.ones((H, W)),
                 psf=self.psf)
     src = PointSource(PixPos(2.3, 2.4), Flux(100.))
     tr = Tractor([tim], [src])
     mod = tr.getModelImage(0)
     assert (mod.shape == (H, W))
     #
     mm = ModelMask(0, 0, W, H)
     #patch = src.getModelPatch(tim, modelMask=mm)
     #upatch = src.getUnitFluxModelPatch(img, modelMask=mm)
     psfpatch = self.psf.getPointSourcePatch(-3.1, 2.4, modelMask=mm)
Exemple #3
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 ModelMask
     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 = SimpleGalaxy(pos, NanoMaggies(**{band: 1.}))
     S = 32
     mm = ModelMask(int(x - S), int(y - S), 2 * S + 1, 2 * S + 1)
     galmod = gal.getModelPatch(tim, modelMask=mm).patch
     galmod = np.maximum(0, galmod)
     galmod /= galmod.sum()
     galnorm = np.sqrt(np.sum(galmod**2))
     return galnorm
Exemple #4
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)
Exemple #5
0
    def getParamDerivatives(self, img, modelMask=None):
        pos0 = self.getPosition()
        (px0, py0) = img.getWcs().positionToPixel(pos0, self)
        counts = img.getPhotoCal().brightnessToCounts(self.brightness)

        minsb = img.modelMinval
        if counts > 0:
            minval = minsb / counts
        else:
            minval = None

        patch0 = self.getUnitFluxModelPatch(img,
                                            px=px0,
                                            py=py0,
                                            minval=minval,
                                            modelMask=modelMask)
        if patch0 is None:
            return [None] * self.numberOfParams()
        derivs = []

        if modelMask is None:
            modelMask = ModelMask.fromExtent(*patch0.getExtent())
        assert (modelMask is not None)

        # FIXME -- would we be better to do central differences in
        # pixel space, and convert to Position via CD matrix?

        # derivatives wrt position
        psteps = pos0.getStepSizes()
        if not self.isParamFrozen('pos'):
            params = pos0.getParams()
            if counts == 0:
                derivs.extend([None] * len(params))
                psteps = []
            for i, pstep in enumerate(psteps):
                oldval = pos0.setParam(i, params[i] + pstep)
                (px, py) = img.getWcs().positionToPixel(pos0, self)
                pos0.setParam(i, oldval)
                patchx = self.getUnitFluxModelPatch(img,
                                                    px=px,
                                                    py=py,
                                                    minval=minval,
                                                    modelMask=modelMask)
                if patchx is None or patchx.getImage() is None:
                    derivs.append(None)
                    continue
                dx = (patchx - patch0) * (counts / pstep)
                dx.setName('d(%s)/d(pos%i)' % (self.dname, i))
                derivs.append(dx)

        # derivatives wrt brightness
        bsteps = self.brightness.getStepSizes()
        if not self.isParamFrozen('brightness'):
            params = self.brightness.getParams()
            for i, bstep in enumerate(bsteps):
                oldval = self.brightness.setParam(i, params[i] + bstep)
                countsi = img.getPhotoCal().brightnessToCounts(self.brightness)
                self.brightness.setParam(i, oldval)
                df = patch0 * ((countsi - counts) / bstep)
                df.setName('d(%s)/d(bright%i)' % (self.dname, i))
                derivs.append(df)

        # derivatives wrt shape
        gsteps = self.shape.getStepSizes()
        if not self.isParamFrozen('shape'):
            gnames = self.shape.getParamNames()
            oldvals = self.shape.getParams()
            if counts == 0:
                derivs.extend([None] * len(oldvals))
                gsteps = []
            for i, gstep in enumerate(gsteps):
                oldval = self.shape.setParam(i, oldvals[i] + gstep)
                patchx = self.getUnitFluxModelPatch(img,
                                                    px=px0,
                                                    py=py0,
                                                    minval=minval,
                                                    modelMask=modelMask)
                self.shape.setParam(i, oldval)
                if patchx is None:
                    print('patchx is None:')
                    print('  ', self)
                    print('  stepping galaxy shape',
                          self.shape.getParamNames()[i])
                    print('  stepped', gsteps[i])
                    print('  to', self.shape.getParams()[i])
                    derivs.append(None)
                    continue
                dx = (patchx - patch0) * (counts / gstep)
                dx.setName('d(%s)/d(%s)' % (self.dname, gnames[i]))
                derivs.append(dx)
        return derivs
Exemple #6
0
    def test_gal(self):

        if ps is not None:
            import pylab as plt

        pos = PixPos(49.5, 50.)
        pos0 = pos
        bright = NanoMaggies(g=1., r=2.)
        shape = GalaxyShape(2., 0.5, 45.)

        #psf = GaussianMixturePSF(1., 0., 0., 4., 4., 0.)
        psf = GaussianMixturePSF(1., 0., 0., 6., 6., -1.)
        #psf = GaussianMixturePSF(1., 0., 0., 9., 9., -1.)
        #psf = GaussianMixturePSF(1., 0., 0., 16., 16., -1.)

        H, W = 100, 100
        tim = Image(
            data=np.zeros((H, W), np.float32),
            inverr=np.ones((H, W), np.float32),
            psf=psf,
            photocal=LinearPhotoCal(1., band='r'),
        )

        psf0 = tim.psf

        # base class
        #gal1 = Galaxy(pos, bright, shape)

        gal1 = ExpGalaxy(pos, bright, shape)

        self.assertEqual(gal1.shape.ab, 0.5)
        print('gal:', gal1)
        print('gal:', str(gal1))
        # harsh
        self.assertEqual(
            str(gal1),
            'ExpGalaxy at pixel (49.50, 50.00) with NanoMaggies: g=22.5, r=21.7 and Galaxy Shape: re=2.00, ab=0.50, phi=45.0'
        )
        self.assertEqual(
            repr(gal1),
            'ExpGalaxy(pos=PixPos[49.5, 50.0], brightness=NanoMaggies: g=22.5, r=21.7, shape=re=2, ab=0.5, phi=45)'
        )

        derivs = gal1.getParamDerivatives(tim)
        print('Derivs:', derivs)

        self.assertEqual(len(derivs), 7)
        self.assertEqual(len(derivs), gal1.numberOfParams())
        self.assertEqual(len(derivs), len(gal1.getParams()))
        for d in derivs:
            self.assertIsNotNone(d)

        # Set one of the fluxes to zero.
        gal1.brightness.r = 0.

        derivs = gal1.getParamDerivatives(tim)
        print('Derivs:', derivs)
        self.assertEqual(len(derivs), 7)
        for i, d in enumerate(derivs):
            # flux derivatives still non-None
            if i in [2, 3]:
                self.assertIsNotNone(derivs[i])
            else:
                # other derivatives should be None
                self.assertIsNone(derivs[i])

        gal1.brightness.r = 100.

        mod = np.zeros((H, W), np.float32)

        p1 = gal1.getModelPatch(tim)
        print('Model patch:', p1)
        print('patch sum:', p1.patch.sum())

        # Very specific tests...
        self.assertEqual(p1.x0, 16)
        self.assertEqual(p1.y0, 17)
        self.assertEqual(p1.shape, (68, 69))
        self.assertTrue(np.abs(p1.patch.sum() - 100.) < 1e-3)

        p1.addTo(mod)
        print('Mod sum:', mod.sum())

        mh, mw = mod.shape
        xx, yy = np.meshgrid(np.arange(mw), np.arange(mh))
        cx, cy = np.sum(xx * mod) / np.sum(mod), np.sum(yy * mod) / np.sum(mod)
        mxx = np.sum((xx - cx)**2 * mod) / np.sum(mod)
        myy = np.sum((yy - cy)**2 * mod) / np.sum(mod)
        mxy = np.sum((xx - cx) * (yy - cy) * mod) / np.sum(mod)
        print('mod centroid:', cx, cy)
        print('moments:', mxx, myy, mxy)

        self.assertTrue(np.abs(mod.sum() - 100.) < 1e-3)

        if ps is not None:
            plt.clf()
            plt.imshow(mod, interpolation='nearest', origin='lower')
            plt.title('mod')
            plt.colorbar()
            ps.savefig()

        def show_model(modN, mod, name):
            plt.clf()
            sy, sx = slice(40, 60), slice(40, 60)
            plt.subplot(2, 3, 1)
            plt.imshow(modN[sy, sx], interpolation='nearest', origin='lower')
            plt.colorbar()
            plt.title(name)

            if mod is not None:
                plt.subplot(2, 3, 2)
                plt.imshow(mod[sy, sx],
                           interpolation='nearest',
                           origin='lower')
                plt.colorbar()
                plt.title('mod')

                plt.subplot(2, 3, 3)
                mx = np.abs(modN - mod).max()
                plt.imshow((modN - mod)[sy, sx],
                           interpolation='nearest',
                           origin='lower',
                           vmin=-mx,
                           vmax=mx)
                plt.colorbar()
                plt.title('%s - mod' % name)

                plt.subplot(2, 3, 6)
                plt.imshow(modN - mod, interpolation='nearest', origin='lower')
                plt.colorbar()
                plt.title('%s - mod' % name)

            plt.subplot(2, 3, 4)
            mx = modN.max()
            plt.imshow(np.log10(modN),
                       vmin=np.log10(mx) - 6,
                       vmax=np.log10(mx),
                       interpolation='nearest',
                       origin='lower')
            plt.colorbar()
            plt.title('log %s' % name)

            if mod is not None:
                plt.subplot(2, 3, 5)
                plt.imshow(np.log10(mod),
                           vmin=np.log10(mx) - 6,
                           vmax=np.log10(mx),
                           interpolation='nearest',
                           origin='lower')
                plt.colorbar()
                plt.title('log mod')
            ps.savefig()

        # Test with ModelMask
        mm = ModelMask(25, 25, 50, 50)
        p2 = gal1.getModelPatch(tim, modelMask=mm)
        mod2 = np.zeros((H, W), np.float32)
        p2.addTo(mod2)
        print('Patch:', p2)
        self.assertEqual(p2.x0, 25)
        self.assertEqual(p2.y0, 25)
        self.assertEqual(p2.shape, (50, 50))
        print('patch sum:', p2.patch.sum())
        print('Mod sum:', mod2.sum())
        self.assertTrue(np.abs(mod2.sum() - 100.) < 1e-3)

        if ps is not None:
            show_model(mod2, mod, 'mod2')

        print('Diff between mods:', np.abs(mod - mod2).max())
        self.assertTrue(np.abs(mod - mod2).max() < 1e-6)

        # Test with a ModelMask with a binary map of pixels of interest
        mm3 = ModelMask(30, 29, np.ones((40, 40), bool))
        p3 = gal1.getModelPatch(tim, modelMask=mm3)
        mod3 = np.zeros((H, W), np.float32)
        p3.addTo(mod3)
        print('Patch:', p3)
        self.assertEqual(p3.x0, 30)
        self.assertEqual(p3.y0, 29)
        self.assertEqual(p3.shape, (40, 40))
        print('patch sum:', p3.patch.sum())
        print('Mod sum:', mod3.sum())
        self.assertTrue(np.abs(mod3.sum() - 100.) < 1e-3)
        print('Diff between mods:', np.abs(mod3 - mod).max())
        self.assertTrue(np.abs(mod3 - mod).max() < 1e-6)

        if ps is not None:
            show_model(mod3, mod, 'mod3')

        # Test with a PixelizedPSF (FFT method), created from the Gaussian PSF
        # image (so we can check consistency)
        psfpatch = tim.psf.getPointSourcePatch(24.,
                                               24.,
                                               modelMask=ModelMask(
                                                   0, 0, 50, 50))
        print('PSF patch:', psfpatch)
        tim.psf = PixelizedPSF(psfpatch.patch[:49, :49])
        pixpsf = tim.psf

        # No modelmask
        print()
        print('Rendering mod4')
        p4 = gal1.getModelPatch(tim)
        mod4 = np.zeros((H, W), np.float32)
        p4.addTo(mod4)
        print('Patch:', p4)

        cx, cy = np.sum(xx * mod4) / np.sum(mod4), np.sum(
            yy * mod4) / np.sum(mod4)
        mxx = np.sum((xx - cx)**2 * mod4) / np.sum(mod4)
        myy = np.sum((yy - cy)**2 * mod4) / np.sum(mod4)
        mxy = np.sum((xx - cx) * (yy - cy) * mod4) / np.sum(mod4)
        print('mod centroid:', cx, cy)
        print('moments:', mxx, myy, mxy)

        if ps is not None:
            show_model(mod4, mod, 'mod4')

        # These assertions are fairly arbitrary...
        self.assertEqual(p4.x0, 6)
        self.assertEqual(p4.y0, 7)
        self.assertEqual(p4.shape, (88, 89))
        print('patch sum:', p4.patch.sum())
        print('Mod sum:', mod4.sum())
        self.assertTrue(np.abs(mod4.sum() - 100.) < 1e-3)
        print('Diff between mods:', np.abs(mod4 - mod).max())
        #self.assertTrue(np.abs(mod4 - mod).max() < 1e-6)
        self.assertTrue(np.abs(mod4 - mod).max() < 2e-3)

        import tractor.galaxy

        if ps is not None:
            #pp = [49.0, 49.1, 49.2, 49.3, 49.4, 49.5, 49.6, 49.7, 49.8, 49.9, 50.]
            #pp = np.arange(48, 51, 0.1)

            plt.clf()

            for L in [3, 5, 7]:
                tractor.galaxy.fft_lanczos_order = L
                CX = []
                pp = np.arange(49, 50.1, 0.1)
                gal1copy = gal1.copy()
                for p in pp:
                    gal1copy.pos = PixPos(p, 50.)
                    tim.psf = psf
                    newmod = np.zeros((H, W), np.float32)
                    p1 = gal1copy.getModelPatch(tim)
                    p1.addTo(newmod)
                    #mod[:,:] = newmod

                    tim.psf = pixpsf
                    modX = np.zeros((H, W), np.float32)
                    p1 = gal1copy.getModelPatch(tim)
                    p1.addTo(modX)

                    print('p=', p)
                    cx, cy = np.sum(xx * modX) / np.sum(modX), np.sum(
                        yy * modX) / np.sum(modX)
                    mxx = np.sum((xx - cx)**2 * modX) / np.sum(modX)
                    myy = np.sum((yy - cy)**2 * modX) / np.sum(modX)
                    mxy = np.sum((xx - cx) * (yy - cy) * modX) / np.sum(modX)
                    print('mod centroid:', cx, cy)
                    print('moments:', mxx, myy, mxy)
                    CX.append(cx)

                plt.plot(pp,
                         np.array(CX) - np.array(pp),
                         '-',
                         label='Lanczos-%i' % L)

                #show_model(modX, newmod, 'mod4(%.1f)' % p)

                # plt.clf()
                # plt.subplot(2,1,1)
                # plt.plot(mod[H/2,:], 'k-')
                # plt.plot(modX[H/2,:], 'r-')
                # plt.subplot(2,1,2)
                # plt.plot(modX[H/2,:] - mod[H/2,:], 'r-')
                # plt.suptitle('mod4(%.1f)' % p)
                # ps.savefig()

            # plt.clf()
            # plt.plot(pp, CX, 'b-')
            # plt.plot(pp, pp, 'k-', alpha=0.25)
            # plt.xlabel('Pixel position')
            # plt.ylabel('Centroid')
            # plt.title('Lanczos-3 interpolation of galaxy profile')
            # ps.savefig()

            plt.axhline(0, color='k', alpha=0.25)
            plt.xlabel('Pixel position')
            plt.ylabel('Centroid - Pixel position')
            plt.title('Lanczos interpolation of galaxy profile')
            plt.legend(loc='upper left')
            ps.savefig()

            from astrometry.util.miscutils import lanczos_filter
            plt.clf()
            xx = np.linspace(-(7 + 1), 7 + 1, 300)
            for L in [3, 5, 7]:
                plt.plot(xx, lanczos_filter(L, xx), '-', label='Lancoz-%i' % L)
            plt.title('Lanczos')
            ps.savefig()

        tractor.galaxy.fft_lanczos_order = 3

        # Test with ModelMask with "mm"
        p5 = gal1.getModelPatch(tim, modelMask=mm)
        mod5 = np.zeros((H, W), np.float32)
        p5.addTo(mod5)
        print('Patch:', p5)

        if ps is not None:
            show_model(mod5, mod, 'mod5')

        self.assertEqual(p5.x0, 25)
        self.assertEqual(p5.y0, 25)
        self.assertEqual(p5.shape, (50, 50))
        print('patch sum:', p5.patch.sum())
        print('Mod sum:', mod5.sum())
        self.assertTrue(np.abs(mod5.sum() - 100.) < 1e-3)
        print('Diff between mods:', np.abs(mod5 - mod).max())
        #self.assertTrue(np.abs(mod5 - mod).max() < 1e-6)
        self.assertTrue(np.abs(mod5 - mod).max() < 2e-3)

        # Test with a source center outside the ModelMask.
        # Way outside the ModelMask -> model is None
        gal1.pos = PixPos(200, -50.)
        p6 = gal1.getModelPatch(tim, modelMask=mm)
        self.assertIsNone(p6)

        # Slightly outside the ModelMask
        gal1.pos = PixPos(20., 25.)
        p7 = gal1.getModelPatch(tim, modelMask=mm)
        mod7 = np.zeros((H, W), np.float32)
        p7.addTo(mod7)
        print('Patch:', p7)

        if ps is not None:
            show_model(mod7, mod, 'mod7')

        self.assertEqual(p7.x0, 25)
        self.assertEqual(p7.y0, 25)
        self.assertEqual(p7.shape, (50, 50))
        print('patch sum:', p7.patch.sum())
        print('Mod sum:', mod7.sum())
        #self.assertTrue(np.abs(mod7.sum() - 1.362) < 1e-3)
        self.assertTrue(np.abs(mod7.sum() - 1.963) < 1e-3)

        # Test a HybridPSF
        tim.psf = HybridPixelizedPSF(tim.psf)
        hybridpsf = tim.psf

        # Slightly outside the ModelMask
        gal1.pos = PixPos(20., 25.)
        p8 = gal1.getModelPatch(tim, modelMask=mm)
        mod8 = np.zeros((H, W), np.float32)
        p8.addTo(mod8)
        print('Patch:', p8)

        if ps is not None:
            show_model(mod8, mod, 'mod8')

        self.assertEqual(p8.x0, 25)
        self.assertEqual(p8.y0, 25)
        self.assertEqual(p8.shape, (50, 50))
        print('patch sum:', p8.patch.sum())
        print('Mod sum:', mod8.sum())
        #self.assertTrue(np.abs(mod8.sum() - 1.362) < 1e-3)
        self.assertTrue(np.abs(mod7.sum() - 1.963) < 1e-3)

        if ps is not None:
            plt.clf()
            plt.imshow(mod7, interpolation='nearest', origin='lower')
            plt.colorbar()
            plt.title('Source outside mask')
            ps.savefig()

            plt.clf()
            plt.imshow(mod8, interpolation='nearest', origin='lower')
            plt.colorbar()
            plt.title('Source outside mask, Hybrid PSF')
            ps.savefig()

        # Put the source close to the image edge.
        gal1.pos = PixPos(5., 5.)
        # No model mask
        p9 = gal1.getModelPatch(tim)
        mod9 = np.zeros((H, W), np.float32)
        p9.addTo(mod9)
        print('Patch:', p9)

        if ps is not None:
            show_model(mod9, None, 'mod9')

        #self.assertEqual(p8.x0, 25)
        #self.assertEqual(p8.y0, 25)
        #self.assertEqual(p8.shape, (50,50))
        print('Mod sum:', mod9.sum())
        #self.assertTrue(np.abs(mod9.sum() - 96.98) < 1e-2)
        self.assertTrue(np.abs(mod9.sum() - 94.33) < 1e-2)

        # Zero outside (0,50),(0,50)
        self.assertEqual(np.sum(np.abs(mod9[50:, :])), 0.)
        self.assertEqual(np.sum(np.abs(mod9[:, 50:])), 0.)

        if ps is not None:
            plt.clf()
            plt.imshow(mod9,
                       interpolation='nearest',
                       origin='lower',
                       vmin=0,
                       vmax=1e-12)
            plt.colorbar()
            plt.title('Source near image edge')
            ps.savefig()

        # Source back in the middle.
        # Tight ModelMask
        gal1.pos = pos0
        mm10 = ModelMask(45, 45, 10, 10)
        p10 = gal1.getModelPatch(tim, modelMask=mm10)
        mod10 = np.zeros((H, W), np.float32)
        p10.addTo(mod10)
        print('Patch:', p10)
        self.assertEqual(p10.x0, 45)
        self.assertEqual(p10.y0, 45)
        self.assertEqual(p10.shape, (10, 10))
        print('Mod sum:', mod10.sum())
        #self.assertTrue(np.abs(mod10.sum() - 96.98) < 1e-2)

        # Larger modelMask
        mm11 = ModelMask(30, 30, 40, 40)
        p11 = gal1.getModelPatch(tim, modelMask=mm11)
        mod11 = np.zeros((H, W), np.float32)
        p11.addTo(mod11)
        print('Patch:', p11)
        print('Mod sum:', mod11.sum())
        self.assertTrue(np.abs(mod11.sum() - 100.) < 1e-3)

        if ps is not None:
            plt.clf()
            plt.imshow(mod10, interpolation='nearest', origin='lower')
            plt.colorbar()
            ps.savefig()

            plt.clf()
            plt.imshow(mod11, interpolation='nearest', origin='lower')
            plt.colorbar()
            ps.savefig()

        diff = (mod11 - mod10)[45:55, 45:55]
        print('Max diff:', np.abs(diff).max())
        self.assertTrue(np.abs(diff).max() < 1e-6)

        # DevGalaxy test
        gal1.pos = pos0

        bright2 = bright
        shape2 = GalaxyShape(3., 0.4, 60.)
        gal2 = DevGalaxy(pos, bright2, shape2)

        p12 = gal2.getModelPatch(tim, modelMask=mm)
        mod12 = np.zeros((H, W), np.float32)
        p12.addTo(mod12)
        print('Patch:', p12)
        print('patch sum:', p12.patch.sum())
        print('Mod sum:', mod12.sum())
        self.assertTrue(np.abs(mod12.sum() - 99.95) < 1e-2)

        # Test FixedCompositeGalaxy
        shapeExp = shape
        shapeDev = shape2

        #modExp = mod2
        modExp = mod4
        modDev = mod12

        # Set FracDev = 0 --> equals gal1 in patch2.
        gal3 = FixedCompositeGalaxy(pos, bright, FracDev(0.), shapeExp,
                                    shapeDev)
        print('Testing galaxy:', gal3)
        print('repr', repr(gal3))
        p13 = gal3.getModelPatch(tim, modelMask=mm)
        mod13 = np.zeros((H, W), np.float32)
        p13.addTo(mod13)
        print('Patch:', p13)
        print('patch sum:', p13.patch.sum())
        print('Mod sum:', mod13.sum())

        if ps is not None:
            show_model(mod13, modExp, 'mod13')

        self.assertTrue(np.abs(mod13.sum() - 100.00) < 1e-2)
        print('SAD:', np.sum(np.abs(mod13 - modExp)))
        #self.assertTrue(np.sum(np.abs(mod13 - modExp)) < 1e-8)
        self.assertTrue(np.sum(np.abs(mod13 - modExp)) < 1e-5)

        # Set FracDev = 1 --> equals gal2 in patch12.
        gal3.fracDev.setValue(1.)
        p14 = gal3.getModelPatch(tim, modelMask=mm)
        mod14 = np.zeros((H, W), np.float32)
        p14.addTo(mod14)
        print('Patch:', p14)
        print('patch sum:', p14.patch.sum())
        print('Mod sum:', mod14.sum())
        self.assertTrue(np.abs(mod14.sum() - 99.95) < 1e-2)
        print('SAD:', np.sum(np.abs(mod14 - modDev)))
        #self.assertTrue(np.sum(np.abs(mod14 - modDev)) < 1e-8)
        self.assertTrue(np.sum(np.abs(mod14 - modDev)) < 1e-5)

        # Set FracDev = 0.5 --> equals mean
        gal3.fracDev = SoftenedFracDev(0.5)
        p15 = gal3.getModelPatch(tim, modelMask=mm)
        mod15 = np.zeros((H, W), np.float32)
        p15.addTo(mod15)
        print('Patch:', p15)
        print('patch sum:', p15.patch.sum())
        print('Mod sum:', mod15.sum())
        self.assertTrue(np.abs(mod15.sum() - 99.98) < 1e-2)
        target = (modDev + modExp) / 2.
        print('SAD:', np.sum(np.abs(mod15 - target)))
        self.assertTrue(np.sum(np.abs(mod15 - target)) < 1e-5)

        derivs = gal3.getParamDerivatives(tim)
        print('Derivs:', derivs)
        self.assertEqual(len(derivs), 11)

        # CompositeGalaxy

        gal4 = CompositeGalaxy(pos, bright, shapeExp, bright, shapeDev)
        print('Testing galaxy:', gal4)
        print('repr', repr(gal4))

        p16 = gal4.getModelPatch(tim, modelMask=mm)
        mod16 = np.zeros((H, W), np.float32)
        p16.addTo(mod16)
        print('Patch:', p16)
        print('patch sum:', p16.patch.sum())
        print('Mod sum:', mod16.sum())
        self.assertTrue(np.abs(mod16.sum() - 199.95) < 1e-2)
        target = (modDev + modExp)
        print('SAD:', np.sum(np.abs(mod16 - target)))
        self.assertTrue(np.sum(np.abs(mod16 - target)) < 1e-5)

        derivs = gal4.getParamDerivatives(tim)
        print('Derivs:', derivs)
        self.assertEqual(len(derivs), 12)

        p17, p18 = gal4.getUnitFluxModelPatches(tim, modelMask=mm)
        mod17 = np.zeros((H, W), np.float32)
        mod18 = np.zeros((H, W), np.float32)
        p17.addTo(mod17)
        p18.addTo(mod18)
        print('SAD', np.sum(np.abs(mod17 * 100. - modExp)))
        print('SAD', np.sum(np.abs(mod18 * 100. - modDev)))
        self.assertTrue(np.abs(mod17 * 100. - modExp).sum() < 1e-5)
        self.assertTrue(np.abs(mod18 * 100. - modDev).sum() < 1e-5)

        # SersicGalaxy
        # gal5 = SersicGalaxy(pos, bright, shapeExp, SersicIndex(1.))
        #
        # tim.psf = psf0
        #
        # p19 = gal5.getModelPatch(tim, modelMask=mm)
        # mod19 = np.zeros((H,W), np.float32)
        # p19.addTo(mod19)
        #
        # if ps is not None:
        #     plt.clf()
        #     plt.imshow(mod19, interpolation='nearest', origin='lower')
        #     plt.colorbar()
        #     plt.title('Sersic n=1')
        #     ps.savefig()
        #
        #     plt.clf()
        #     plt.imshow(mod19 - modExp, interpolation='nearest', origin='lower')
        #     plt.colorbar()
        #     plt.title('Sersic n=1 - EXP')
        #     ps.savefig()
        #
        # print('Patch:', p19)
        # print('patch sum:', p19.patch.sum())
        # print('Mod sum:', mod19.sum())
        # self.assertTrue(np.abs(mod19.sum() - 100.00) < 1e-2)
        # target = modExp
        # # print('SAD:', np.sum(np.abs(mod19 - target)))
        # # self.assertTrue(np.sum(np.abs(mod19 - target)) < 1e-5)
        #
        # gal5.sersicindex.setValue(4.)
        # gal5.shape = shapeDev
        #
        # p20 = gal5.getModelPatch(tim, modelMask=mm)
        # mod20 = np.zeros((H,W), np.float32)
        # p20.addTo(mod20)
        # print('Patch:', p20)
        # print('patch sum:', p20.patch.sum())
        # print('Mod sum:', mod20.sum())
        #
        # if ps is not None:
        #     plt.clf()
        #     plt.imshow(mod20, interpolation='nearest', origin='lower')
        #     plt.colorbar()
        #     plt.title('Sersic n=4')
        #     ps.savefig()
        #
        #     plt.clf()
        #     plt.imshow(mod20 - modDev, interpolation='nearest', origin='lower')
        #     plt.colorbar()
        #     plt.title('Sersic n=4 - DEV')
        #     ps.savefig()
        #
        # self.assertTrue(np.abs(mod20.sum() - 99.95) < 1e-2)
        # target = modDev
        # print('SAD:', np.sum(np.abs(mod20 - target)))
        # self.assertTrue(np.sum(np.abs(mod20 - target)) < 1e-5)

        # A galaxy that will wrap around
        from tractor.ellipses import EllipseE
        mmX = ModelMask(20, 20, 60, 60)
        shapeX = EllipseE(20., 0.7, 0.7)
        tim.psf = pixpsf
        gal6 = DevGalaxy(pos0, bright, shapeX)

        p21 = gal6.getModelPatch(tim, modelMask=mmX)
        mod21 = np.zeros((H, W), np.float32)
        p21.addTo(mod21)

        if ps is not None:
            plt.clf()
            plt.imshow(mod21, interpolation='nearest', origin='lower')
            plt.colorbar()
            plt.title('Pixelized PSF')
            ps.savefig()

        tim.psf = hybridpsf

        p22 = gal6.getModelPatch(tim, modelMask=mmX)
        mod22 = np.zeros((H, W), np.float32)
        p22.addTo(mod22)

        # Horizontal slice through the middle of the galaxy
        m21 = mod21[49, :]
        m22 = mod22[49, :]

        if ps is not None:
            plt.clf()
            plt.imshow(mod22, interpolation='nearest', origin='lower')
            plt.colorbar()
            plt.title('Hybrid PSF')
            ps.savefig()

            #print('m22', m22)

            plt.clf()
            plt.plot(m21, 'r-')
            plt.plot(m22, 'b-')
            plt.yscale('symlog', linthreshy=1e-8)
            ps.savefig()

        imx = np.argmax(m22)
        diff = np.diff(m22[:imx + 1])
        # Assert monotonic up to the peak (from the left)
        # (low-level jitter/wrap-around is allowed)
        self.assertTrue(np.all(np.logical_or(np.abs(diff) < 1e-9, diff > 0)))

        diff = np.diff(m22[imx:])
        # Assert monotonic decreasing after to the peak
        # (low-level jitter/wrap-around is allowed)
        self.assertTrue(np.all(np.logical_or(np.abs(diff) < 1e-9, diff < 0)))

        # Assert that wrap-around exists for PixelizedPsf model

        diff = np.diff(m21[:imx + 1])
        self.assertFalse(np.all(np.logical_or(np.abs(diff) < 1e-9, diff > 0)))

        diff = np.diff(m21[imx:])
        self.assertFalse(np.all(np.logical_or(np.abs(diff) < 1e-9, diff < 0)))
Exemple #7
0
    y = h // 2
psfmod = tim.psf.getPointSourcePatch(x, y).patch

from tractor.galaxy import ExpGalaxy
from tractor.ellipses import EllipseE
from tractor.patch import ModelMask

band = tim.band
if x is None:
    x = w // 2
if y is None:
    y = h // 2
pos = tim.wcs.pixelToPosition(x, y)
gal = SimpleGalaxy(pos, NanoMaggies(**{band: 1.}))
S = 32
mm = ModelMask(int(x - S), int(y - S), 2 * S + 1, 2 * S + 1)
galmod = gal.getModelPatch(tim, modelMask=mm).patch
print('Galaxy model shape', galmod.shape)

mx = max(psfmod.max(), galmod.max())
mn = min(psfmod.min(), galmod.min())
ima = dict(interpolation='nearest', origin='lower', vmin=mn, vmax=mx)

print('Range', mn, mx)

plt.clf()
plt.subplot(1, 2, 1)
plt.imshow(psfmod, **ima)
plt.title('PSF model')
plt.subplot(1, 2, 2)
plt.imshow(galmod, **ima)