class SimpleGalaxy(ExpGalaxy): shape = EllipseE(0.45, 0., 0.) def __init__(self, *args): super(SimpleGalaxy, self).__init__(*args) self.shape = SimpleGalaxy.shape def __str__(self): return (self.name + ' at ' + str(self.pos) + ' with ' + str(self.brightness)) def __repr__(self): return (self.name + '(pos=' + repr(self.pos) + ', brightness=' + repr(self.brightness) + ')') @staticmethod def getNamedParams(): return dict(pos=0, brightness=1) def getName(self): return 'SimpleGalaxy' ### HACK -- for Galaxy.getParamDerivatives() def isParamFrozen(self, pname): if pname == 'shape': return True return super(SimpleGalaxy, self).isParamFrozen(pname)
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
def main(): import optparse from astrometry.util.plotutils import PlotSequence from astrometry.util.util import Tan parser = optparse.OptionParser(usage='%prog [options] incat.fits out.fits') parser.add_option('-r', '--ralo', dest='ralo', type=float, help='Minimum RA') parser.add_option('-R', '--rahi', dest='rahi', type=float, help='Maximum RA') parser.add_option('-d', '--declo', dest='declo', type=float, help='Minimum Dec') parser.add_option('-D', '--dechi', dest='dechi', type=float, help='Maximum Dec') parser.add_option('-b', '--band', dest='bands', action='append', type=int, default=[], help='WISE band to photometer (default: 1,2)') parser.add_option('-u', '--unwise', dest='unwise_dir', default='unwise-coadds', help='Directory containing unWISE coadds') parser.add_option('--no-ceres', dest='ceres', action='store_false', default=True, help='Use scipy lsqr rather than Ceres Solver?') parser.add_option('--ceres-block', '-B', dest='ceresblock', type=int, default=8, help='Ceres image block size (default: %default)') parser.add_option('--plots', dest='plots', default=False, action='store_true') parser.add_option('--save-fits', dest='save_fits', default=False, action='store_true') # parser.add_option('--ellipses', action='store_true', # help='Assume catalog shapes are ellipse descriptions (not r,ab,phi)') # parser.add_option('--ra', help='Center RA') # parser.add_option('--dec', help='Center Dec') # parser.add_option('--width', help='Degrees width (in RA*cos(Dec))') # parser.add_option('--height', help='Degrees height (Dec)') opt, args = parser.parse_args() if len(args) != 2: parser.print_help() sys.exit(-1) if len(opt.bands) == 0: opt.bands = [1, 2] # Allow specifying bands like "123" bb = [] for band in opt.bands: for s in str(band): bb.append(int(s)) opt.bands = bb print('Bands', opt.bands) ps = None if opt.plots: ps = PlotSequence('unwise') infn, outfn = args T = fits_table(infn) print('Read', len(T), 'sources from', infn) if opt.declo is not None: T.cut(T.dec >= opt.declo) if opt.dechi is not None: T.cut(T.dec <= opt.dechi) # Let's be a bit smart about RA wrap-around. Compute the 'center' # of the RA points, use the cross product against that to define # inequality (clockwise-of). r = np.deg2rad(T.ra) x = np.mean(np.cos(r)) y = np.mean(np.sin(r)) rr = np.hypot(x, y) x /= rr y /= rr midra = np.rad2deg(np.arctan2(y, x)) midra += 360. * (midra < 0) xx = np.cos(r) yy = np.sin(r) T.cross = x * yy - y * xx minra = T.ra[np.argmin(T.cross)] maxra = T.ra[np.argmax(T.cross)] if opt.ralo is not None: r = np.deg2rad(opt.ralo) xx = np.cos(r) yy = np.sin(r) crosscut = x * yy - y * xx T.cut(T.cross >= crosscut) print('Cut to', len(T), 'with RA >', opt.ralo) if opt.rahi is not None: r = np.deg2rad(opt.rahi) xx = np.cos(r) yy = np.sin(r) crosscut = x * yy - y * xx T.cut(T.cross <= crosscut) print('Cut to', len(T), 'with RA <', opt.rahi) if opt.declo is None: opt.declo = T.dec.min() if opt.dechi is None: opt.dechi = T.dec.max() if opt.ralo is None: opt.ralo = T.ra[np.argmin(T.cross)] if opt.rahi is None: opt.rahi = T.ra[np.argmax(T.cross)] T.delete_column('cross') print('RA range:', opt.ralo, opt.rahi) print('Dec range:', opt.declo, opt.dechi) x = np.mean([np.cos(np.deg2rad(r)) for r in (opt.ralo, opt.rahi)]) y = np.mean([np.sin(np.deg2rad(r)) for r in (opt.ralo, opt.rahi)]) midra = np.rad2deg(np.arctan2(y, x)) midra += 360. * (midra < 0) middec = (opt.declo + opt.dechi) / 2. print('RA,Dec center:', midra, middec) pixscale = 2.75 / 3600. H = (opt.dechi - opt.declo) / pixscale dra = 2. * min(np.abs(midra - opt.ralo), np.abs(midra - opt.rahi)) W = dra * np.cos(np.deg2rad(middec)) / pixscale margin = 5 W = int(W) + margin * 2 H = int(H) + margin * 2 print('W,H', W, H) targetwcs = Tan(midra, middec, (W + 1) / 2., (H + 1) / 2., -pixscale, 0., 0., pixscale, float(W), float(H)) #print('Target WCS:', targetwcs) ra0, dec0 = targetwcs.pixelxy2radec(0.5, 0.5) ra1, dec1 = targetwcs.pixelxy2radec(W + 0.5, H + 0.5) roiradecbox = [ra0, ra1, dec0, dec1] #print('ROI RA,Dec box', roiradecbox) tiles = unwise_tiles_touching_wcs(targetwcs) print('Cut to', len(tiles), 'unWISE tiles') disable_galaxy_cache() cols = T.get_columns() all_ptsrcs = not('type' in cols) if not all_ptsrcs: assert('shapeexp' in cols) assert('shapedev' in cols) assert('fracdev' in cols) wanyband = 'w' print('Creating Tractor catalog...') cat = [] for i, t in enumerate(T): pos = RaDecPos(t.ra, t.dec) flux = NanoMaggies(**{wanyband: 1.}) if all_ptsrcs: cat.append(PointSource(pos, flux)) continue tt = t.type.strip() if tt in ['PTSRC', 'STAR', 'S']: cat.append(PointSource(pos, flux)) elif tt in ['EXP', 'E']: shape = EllipseE(*t.shapeexp) cat.append(ExpGalaxy(pos, flux, shape)) elif tt in ['DEV', 'D']: shape = EllipseE(*t.shapedev) cat.append(DevGalaxy(pos, flux, shape)) elif tt in ['COMP', 'C']: eshape = EllipseE(*t.shapeexp) dshape = EllipseE(*t.shapedev) cat.append(FixedCompositeGalaxy(pos, flux, t.fracdev, eshape, dshape)) else: print('Did not understand row', i, 'of input catalog:') t.about() assert(False) W = unwise_forcedphot(cat, tiles, roiradecbox=roiradecbox, bands=opt.bands, unwise_dir=opt.unwise_dir, use_ceres=opt.ceres, ceres_block=opt.ceresblock, save_fits=opt.save_fits, ps=ps) W.writeto(outfn)
EI = EI[np.argsort(EI.r)] print 'r mags:', EI.r for j in range(cols): plt.subplot(rows, cols, k) k += 1 p = EI[j] x,y = int(np.round(p.bx)), int(np.round(p.by)) H,W,planes = img.shape sz = 29 iny,outy = get_overlapping_region(y - sz, y + sz, 0, H-1) inx,outx = get_overlapping_region(x - sz, x + sz, 0, W-1) # print 'outy,outx', outy,outx # print 'iny,inx', iny,inx subimg = np.zeros((sz*2+1, sz*2+1,planes), img.dtype) subimg[outy,outx] = img[iny,inx] ell = EllipseE(p.shapeexp_r, p.shapeexp_e1, p.shapeexp_e2) angle = np.rad2deg(ell.theta) print 'Angle:', angle subimg = scipy.ndimage.rotate(subimg, angle + 90, reshape=False) subimg = subimg[9:-9, 9:-9, :] print 'image shape', subimg.shape dimshow(subimg, ticks=False) ps.savefig()
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)))
def main(): # I read a DESI DR8 target catalog, cut to ELGs, then took a narrow # r-mag slice around the peak of that distribution; # desi/target/catalogs/dr8/0.31.1/targets/main/resolve/targets-dr8-hp-1,5,11,50,55,60,83,84,86,89,91,98,119,155,158,174,186,187,190.fits') # Then took the median g and z mags # And opened the coadd invvar mags for a random brick in that footprint # (0701p000) to find the median per-pixel invvars. r = 23.0 g = 23.4 z = 22.2 # Selecting EXPs, the peak of the shapeexp_r was ~ 0.75". r_e = 0.75 # Image properties: giv = 77000. riv = 27000. ziv = 8000. # PSF sizes were roughly equal, 1.16, 1.17, 1.19" # -> sigma = 1.9 DECam pixels psf_sigma = 1.9 H, W = 1000, 1000 seed = 42 np.random.seed(seed) ra, dec = 70., 1. brick = BrickDuck(ra, dec, 'custom-0700p010') wcs = wcs_for_brick(brick, W=W, H=H) bands = 'grz' tims = [] for band, iv in zip(bands, [giv, riv, ziv]): img = np.zeros((H, W), np.float32) tiv = np.zeros_like(img) + iv s = psf_sigma**2 psf = GaussianMixturePSF(1., 0., 0., s, s, 0.) twcs = ConstantFitsWcs(wcs) sky = ConstantSky(0.) photocal = LinearPhotoCal(1., band=band) tai = TAITime(None, mjd=55700.) tim = tractor.Image(data=img, invvar=tiv, psf=psf, wcs=twcs, sky=sky, photocal=photocal, name='fake %s' % band, time=tai) tim.skyver = ('1', '1') tim.psfver = ('1', '1') tim.plver = '1' tim.x0 = tim.y0 = 0 tim.subwcs = wcs tim.psfnorm = 1. / (2. * np.sqrt(np.pi) * psf_sigma) tim.galnorm = tim.psfnorm tim.propid = '2020A-000' tim.band = band tim.dq = None tim.sig1 = 1. / np.sqrt(iv) tim.psf_sigma = psf_sigma tim.primhdr = fitsio.FITSHDR() tims.append(tim) # Simulated catalog gflux = NanoMaggies.magToNanomaggies(g) rflux = NanoMaggies.magToNanomaggies(r) zflux = NanoMaggies.magToNanomaggies(z) box = 50 CX, CY = np.meshgrid(np.arange(box // 2, W, box), np.arange(box // 2, H, box)) ny, nx = CX.shape BA, PHI = np.meshgrid(np.linspace(0.1, 1.0, nx), np.linspace(0., 180., ny)) cat = [] for cx, cy, ba, phi in zip(CX.ravel(), CY.ravel(), BA.ravel(), PHI.ravel()): #print('x,y %.1f,%.1f, ba %.2f, phi %.2f' % (cx, cy, ba, phi)) r, d = wcs.pixelxy2radec(cx + 1, cy + 1) src = ExpGalaxy(RaDecPos(r, d), NanoMaggies(order=bands, g=gflux, r=rflux, z=zflux), EllipseE.fromRAbPhi(r_e, ba, phi)) cat.append(src) from legacypipe.catalog import prepare_fits_catalog TT = fits_table() TT.bx = CX.ravel() TT.by = CY.ravel() TT.ba = BA.ravel() TT.phi = PHI.ravel() tcat = tractor.Catalog(*cat) T2 = prepare_fits_catalog(tcat, None, TT, bands, save_invvars=False) T2.writeto('sim-cat.fits') tr = Tractor(tims, cat) for band, tim in zip(bands, tims): mod = tr.getModelImage(tim) mod += np.random.standard_normal( size=tim.shape) * 1. / tim.getInvError() fitsio.write('sim-%s.fits' % band, mod, clobber=True) tim.data = mod ccds = fits_table() ccds.filter = np.array([f for f in bands]) ccds.ccd_cuts = np.zeros(len(ccds), np.int16) ccds.imgfn = np.array([tim.name for tim in tims]) ccds.propid = np.array(['2020A-000'] * len(ccds)) ccds.fwhm = np.zeros(len(ccds), np.float32) + psf_sigma * 2.35 ccds.mjd_obs = np.zeros(len(ccds)) ccds.camera = np.array(['fake'] * len(ccds)) survey = FakeLegacySurvey(ccds, tims) import logging verbose = False if verbose == 0: lvl = logging.INFO else: lvl = logging.DEBUG logging.basicConfig(level=lvl, format='%(message)s', stream=sys.stdout) # tractor logging is *soooo* chatty logging.getLogger('tractor.engine').setLevel(lvl + 10) run_brick(None, survey, radec=(ra, dec), width=W, height=H, do_calibs=False, gaia_stars=False, large_galaxies=False, tycho_stars=False, forceall=True, outliers=False) #, stages=['image_coadds'])
def get_galaxy_sources(galaxies, bands): from legacypipe.catalog import fits_reverse_typemap from legacypipe.survey import (LegacySersicIndex, LegacyEllipseWithPriors, LogRadius, RexGalaxy) from tractor import NanoMaggies, RaDecPos, PointSource from tractor.ellipses import EllipseE, EllipseESoft from tractor.galaxy import DevGalaxy, ExpGalaxy from tractor.sersic import SersicGalaxy # Factor of HyperLEDA to set the galaxy max radius radius_max_factor = 2. srcs = [None for g in galaxies] # If we have pre-burned galaxies, re-create the Tractor sources for them. I, = np.nonzero(galaxies.preburned) for ii, g in zip(I, galaxies[I]): typ = fits_reverse_typemap[g.type.strip()] pos = RaDecPos(g.ra, g.dec) fluxes = dict([(band, g.get('flux_%s' % band)) for band in bands]) bright = NanoMaggies(order=bands, **fluxes) shape = None # put the Rex branch first, because Rex is a subclass of ExpGalaxy! if issubclass(typ, RexGalaxy): assert (np.isfinite(g.shape_r)) logre = np.log(g.shape_r) shape = LogRadius(logre) # set prior max at 2x SGA radius shape.setMaxLogRadius(logre + np.log(radius_max_factor)) elif issubclass(typ, (DevGalaxy, ExpGalaxy, SersicGalaxy)): assert (np.isfinite(g.shape_r)) assert (np.isfinite(g.shape_e1)) assert (np.isfinite(g.shape_e2)) shape = EllipseE(g.shape_r, g.shape_e1, g.shape_e2) # switch to softened ellipse (better fitting behavior) shape = EllipseESoft.fromEllipseE(shape) # and then to our custom ellipse class logre = shape.logre shape = LegacyEllipseWithPriors(logre, shape.ee1, shape.ee2) assert (np.all(np.isfinite(shape.getParams()))) # set prior max at 2x SGA radius shape.setMaxLogRadius(logre + np.log(radius_max_factor)) if issubclass(typ, PointSource): src = typ(pos, bright) # this catches Rex too elif issubclass(typ, (DevGalaxy, ExpGalaxy)): src = typ(pos, bright, shape) elif issubclass(typ, (SersicGalaxy)): assert (np.isfinite(g.sersic)) sersic = LegacySersicIndex(g.sersic) src = typ(pos, bright, shape, sersic) else: raise RuntimeError('Unknown preburned SGA source type "%s"' % typ) debug('Created', src) assert (np.isfinite(src.getLogPrior())) srcs[ii] = src # SGA parent catalog: 'preburned' is not set # This also can happen in the preburned/ellipse catalog when fitting # fails, or no-grz, etc. I, = np.nonzero(np.logical_not(galaxies.preburned)) for ii, g in zip(I, galaxies[I]): # Initialize each source with an exponential disk-- fluxes = dict([(band, NanoMaggies.magToNanomaggies(g.mag)) for band in bands]) assert (np.all(np.isfinite(list(fluxes.values())))) rr = g.radius * 3600. / 2 # factor of two accounts for R(25)-->reff [arcsec] assert (np.isfinite(rr)) assert (np.isfinite(g.ba)) assert (np.isfinite(g.pa)) ba = g.ba if ba <= 0.0 or ba > 1.0: # Make round! ba = 1.0 logr, ee1, ee2 = EllipseESoft.rAbPhiToESoft( rr, ba, 180 - g.pa) # note the 180 rotation assert (np.isfinite(logr)) assert (np.isfinite(ee1)) assert (np.isfinite(ee2)) shape = LegacyEllipseWithPriors(logr, ee1, ee2) shape.setMaxLogRadius(logr + np.log(radius_max_factor)) src = ExpGalaxy(RaDecPos(g.ra, g.dec), NanoMaggies(order=bands, **fluxes), shape) assert (np.isfinite(src.getLogPrior())) src.needs_initial_flux = True srcs[ii] = src return srcs
def add_tractor_sources(obj_cat, sources, w, shape_method='manual'): ''' Add tractor sources to the sources list. Parameters: ---------- obj_cat: astropy Table, objects catalogue. sources: list, to which we will add objects. w: wcs object. shape_method: string, 'manual' or 'decals'. If 'manual', it will adopt the manually measured shapes. If 'decals', it will adopt 'DECaLS' tractor shapes. Returns: -------- sources: list of sources. ''' from tractor import NullWCS, NullPhotoCal, ConstantSky from tractor.galaxy import GalaxyShape, DevGalaxy, ExpGalaxy, CompositeGalaxy from tractor.psf import Flux, PixPos, PointSource, PixelizedPSF, Image, Tractor from tractor.ellipses import EllipseE obj_type = np.array(list(map(lambda st: st.rstrip(' '), obj_cat['type']))) comp_galaxy = obj_cat[obj_type == 'COMP'] dev_galaxy = obj_cat[obj_type == 'DEV'] exp_galaxy = obj_cat[obj_type == 'EXP'] rex_galaxy = obj_cat[obj_type == 'REX'] psf_galaxy = obj_cat[np.logical_or(obj_type =='PSF', obj_type==' ')] if shape_method is 'manual': # Using manually measured shapes if sources is None: sources = [] for obj in obj_cat: pos_x, pos_y = w.wcs_world2pix([[obj['ra'], obj['dec']]], 1)[0] if obj['type'].rstrip(' ') == 'COMP': sources.append( CompositeGalaxy( PixPos(pos_x, pos_y), Flux(0.4 * obj['flux']), GalaxyShape(obj['a_arcsec'] * 0.8, 0.9, 90.0 + obj['theta'] * 180.0 / np.pi), Flux(0.6 * obj['flux']), GalaxyShape(obj['a_arcsec'], obj['b_arcsec'] / obj['a_arcsec'], 90.0 + obj['theta'] * 180.0 / np.pi))) elif obj['type'].rstrip(' ') == 'DEV': sources.append( DevGalaxy( PixPos(pos_x, pos_y), Flux(obj['flux']), GalaxyShape(obj['a_arcsec'], (obj['b_arcsec'] / obj['a_arcsec']), (90.0 + obj['theta'] * 180.0 / np.pi)))) elif obj['type'].rstrip(' ') == 'EXP': sources.append( ExpGalaxy( PixPos(pos_x, pos_y), Flux(obj['flux']), GalaxyShape(obj['a_arcsec'], (obj['b_arcsec'] / obj['a_arcsec']), (90.0 + obj['theta'] * 180.0 / np.pi)))) elif obj['type'].rstrip(' ') == 'REX': sources.append( ExpGalaxy( PixPos(pos_x, pos_y), Flux(obj['flux']), GalaxyShape(obj['a_arcsec'], (obj['b_arcsec'] / obj['a_arcsec']), (90.0 + obj['theta'] * 180.0 / np.pi)))) elif obj['type'].rstrip(' ') == 'PSF' or obj['type'].rstrip(' ') == ' ': sources.append(PointSource(PixPos(pos_x, pos_y), Flux(obj['flux']))) print("Now you have %d sources" % len(sources)) elif shape_method is 'decals': ## Using DECaLS shapes if sources is None: sources = [] for obj in comp_galaxy: pos_x, pos_y = w.wcs_world2pix([[obj['ra'], obj['dec']]], 1)[0] sources.append( CompositeGalaxy( PixPos(pos_x, pos_y), Flux(0.4 * obj['flux']), EllipseE(obj['shapeexp_r'], obj['shapeexp_e1'], obj['shapeexp_e2']), Flux(0.6 * obj['flux']), EllipseE(obj['shapedev_r'], obj['shapedev_e1'], obj['shapedev_e2']))) for obj in dev_galaxy: pos_x, pos_y = w.wcs_world2pix([[obj['ra'], obj['dec']]], 1)[0] sources.append( DevGalaxy( PixPos(pos_x, pos_y), Flux(obj['flux']), EllipseE(obj['shapedev_r'], obj['shapedev_e1'], -obj['shapedev_e2']))) for obj in exp_galaxy: pos_x, pos_y = w.wcs_world2pix([[obj['ra'], obj['dec']]], 1)[0] sources.append( ExpGalaxy( PixPos(pos_x, pos_y), Flux(obj['flux']), EllipseE(obj['shapeexp_r'], obj['shapeexp_e1'], -obj['shapeexp_e2']))) for obj in rex_galaxy: #if obj['point_source'] > 0.0: # sources.append(PointSource(PixPos(w.wcs_world2pix([[obj['ra'], obj['dec']]],1)[0]), # Flux(obj['flux']))) pos_x, pos_y = w.wcs_world2pix([[obj['ra'], obj['dec']]], 1)[0] sources.append( ExpGalaxy( PixPos(pos_x, pos_y), Flux(obj['flux']), EllipseE(obj['shapeexp_r'], obj['shapeexp_e1'], -obj['shapeexp_e2']))) for obj in psf_galaxy: pos_x, pos_y = w.wcs_world2pix([[obj['ra'], obj['dec']]], 1)[0] sources.append(PointSource(PixPos(pos_x, pos_y), Flux(obj['flux']))) print("Now you have %d sources" % len(sources)) else: raise ValueError('Cannot use this shape method') return sources
tim = Image(data=np.zeros((H, W)), invvar=np.ones((H, W)) / (noisesigma**2), psf=NCircularGaussianPSF([psfsigma], [1.])) types = {'PSF ': 1, 'SIMP': 2, 'EXP ': 3, 'DEV ': 4, 'COMP': 5} g, r = 1, 2 sources = [] for i in range(len(cat['ra'])): x, y = grid.nearest_cell_index(cat['ra'][i], cat['dec'][i]) gflux = cat['decam_flux'][i][g] if gflux <= 0: continue if types[cat['type'][i]] == 1: sources.append(PointSource(PixPos(x, y), Flux(gflux))) elif types[cat['type'][i]] == 2: sources.append( ExpGalaxy(PixPos(x, y), Flux(gflux), EllipseE(0.45, 0., 0.))) elif types[cat['type'][i]] == 3: radius, e1, e2 = cat['shapeexp_r'][i], cat['shapeexp_e1'][i], cat[ 'shapeexp_e2'][i] sources.append( ExpGalaxy(PixPos(x, y), Flux(gflux), EllipseE(radius, e1, e2))) elif types[cat['type'][i]] == 4: radius, e1, e2 = cat['shapedev_r'][i], cat['shapedev_e1'][i], cat[ 'shapedev_e2'][i] sources.append( DevGalaxy(PixPos(x, y), Flux(gflux), EllipseE(radius, e1, e2))) elif types[cat['type'][i]] == 5: fdev = cat['fracdev'][i] exp_r, exp_1, exp_2 = cat['shapeexp_r'][i], cat['shapeexp_e1'][i], cat[ 'shapeexp_e2'][i] dev_r, dev_1, dev_2 = cat['shapedev_r'][i], cat['shapedev_e1'][i], cat[