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