def scan_dchisq(seeing, target_dchisq, ps, e1=0.): pixscale = 0.262 psfsigma = seeing / pixscale / 2.35 print('PSF sigma:', psfsigma, 'pixels') psf = GaussianMixturePSF(1., 0., 0., psfsigma**2, psfsigma**2, 0.) sig1 = 0.01 psfnorm = 1./(2. * np.sqrt(np.pi) * psfsigma) detsig1 = sig1 / psfnorm sz = 50 cd = pixscale / 3600. wcs = Tan(0., 0., float(sz/2), float(sz/2), -cd, 0., 0., cd, float(sz), float(sz)) band = 'r' tim = Image(data=np.zeros((sz,sz)), inverr=np.ones((sz,sz)) / sig1, psf=psf, wcs = ConstantFitsWcs(wcs), photocal = LinearPhotoCal(1., band=band)) re_vals = np.logspace(-1., 0., 50) all_runs = [] mods = [] for i,re in enumerate(re_vals): true_src = ExpGalaxy(RaDecPos(0., 0.), NanoMaggies(**{band: 1.}), EllipseE(re, e1, 0.)) print('True source:', true_src) tr = Tractor([tim], [true_src]) tr.freezeParams('images') true_mod = tr.getModelImage(0) dchisq_none = np.sum((true_mod * tim.inverr)**2) scale = np.sqrt(target_dchisq / dchisq_none) true_src.brightness.setParams([scale]) true_mod = tr.getModelImage(0) dchisq_none = np.sum((true_mod * tim.inverr)**2) mods.append(true_mod) tim.data = true_mod exp_src = true_src.copy() psf_src = PointSource(true_src.pos.copy(), true_src.brightness.copy()) simp_src = SimpleGalaxy(true_src.pos.copy(), true_src.brightness.copy()) dchisqs = [] #for src in [psf_src, simp_src, exp_src]: for src in [psf_src, simp_src]: src.freezeParam('pos') #print('Fitting source:', src) #src.printThawedParams() tr.catalog[0] = src tr.optimize_loop() #print('Fitted:', src) mod = tr.getModelImage(0) dchisqs.append(dchisq_none - np.sum(((true_mod - mod) * tim.inverr)**2)) #print('dchisq:', dchisqs[-1]) dchisqs.append(dchisq_none) all_runs.append([re,] + dchisqs) all_runs = np.array(all_runs) re = all_runs[:,0] dchi_psf = all_runs[:,1] dchi_simp = all_runs[:,2] dchi_exp = all_runs[:,3] dchi_ps = np.maximum(dchi_psf, dchi_simp) dchi_cut1 = dchi_ps + 3+9 dchi_cut2 = dchi_ps + dchi_psf * 0.02 dchi_cut3 = dchi_ps + dchi_psf * 0.008 plt.clf() plt.plot(re, dchi_psf, 'k-', label='PSF') plt.plot(re, dchi_simp, 'b-', label='SIMP') plt.plot(re, dchi_exp, 'r-', label='EXP') plt.plot(re, dchi_cut2, 'm--', alpha=0.5, lw=2, label='Cut: 2%') plt.plot(re, dchi_cut3, 'm:', alpha=0.5, lw=2, label='Cut: 0.08%') plt.plot(re, dchi_cut1, 'm-', alpha=0.5, lw=2, label='Cut: 12') plt.xlabel('True r_e (arcsec)') plt.ylabel('dchisq') #plt.legend(loc='lower left') plt.legend(loc='upper right') tt = 'Seeing = %g arcsec, S/N ~ %i' % (seeing, int(np.round(np.sqrt(target_dchisq)))) if e1 != 0.: tt += ', Ellipticity %g' % e1 plt.title(tt) plt.ylim(0.90 * target_dchisq, 1.05 * target_dchisq) # aspect = 1.2 # ax = plt.axis() # dre = (ax[1]-ax[0]) / 20 / aspect # dchi = (ax[3]-ax[2]) / 20 # I = np.linspace(0, len(re_vals)-1, 8).astype(int) # for mod,re in [(mods[i], re_vals[i]) for i in I]: # print('extent:', [re-dre, re+dre, ax[2], ax[2]+dchi]) # plt.imshow(mod, interpolation='nearest', origin='lower', aspect='auto', # extent=[re-dre, re+dre, ax[2], ax[2]+dchi], cmap='gray') # plt.axis(ax) ps.savefig()
def main(): # Where are the data? datadir = os.path.join(os.path.dirname(__file__), 'data-decam') name = 'decam-520206-S16' imagefn = os.path.join(datadir, '%s-image-sub.fits' % name) invvarfn = os.path.join(datadir, '%s-invvar-sub.fits' % name) psfexfn = os.path.join(datadir, '%s-psfex.fits' % name) catfn = os.path.join(datadir, 'tractor-1816p325-sub.fits') # Read the image and inverse-variance maps. image = fitsio.read(imagefn) invvar = fitsio.read(invvarfn) # The DECam inverse-variance maps are unfortunately corrupted # by fpack, causing zeros to become negative. Fix those. invvar[invvar < np.median(invvar) * 0.1] = 0. H, W = image.shape print('Subimage size:', image.shape) # For the PSF model, we need to know what subimage region this is: subimage_offset = (35, 1465) # We also need the calibrated zeropoint. zeropoint = 24.7787 # What filter was this image taken in? (z) prim_header = fitsio.read_header(imagefn) band = prim_header['FILTER'].strip()[0] print('Band:', band) # These DECam images were calibrated so that the zeropoints need # an exposure-time factor, so add that in. exptime = prim_header['EXPTIME'] zeropoint += 2.5 * np.log10(exptime) # Read the PsfEx model file psf = PixelizedPsfEx(psfexfn) # Instantiate a constant pixelized PSF at the image center # (of the subimage) x0, y0 = subimage_offset psf = psf.constantPsfAt(x0 + W / 2., y0 + H / 2.) # Load the WCS model from the header # We convert from the RA---TPV type to RA---SIP header = fitsio.read_header(imagefn, ext=1) wcsobj = wcs_pv2sip_hdr(header, stepsize=10) # We'll just use a rough sky estimate... skyval = np.median(image) # Create the Tractor Image (tim). tim = Image(data=image, invvar=invvar, psf=psf, wcs=ConstantFitsWcs(wcsobj), sky=ConstantSky(skyval), photocal=LinearPhotoCal( NanoMaggies.zeropointToScale(zeropoint), band=band)) # Read the official DECaLS DR3 catalog -- it has only two sources in this subimage. catalog = fits_table(catfn) print('Read', len(catalog), 'sources') print('Source types:', catalog.type) # Create Tractor sources corresponding to these two catalog # entries. # In DECaLS, the "SIMP" type is a round Exponential galaxy with a # fixed 0.45" radius, but we'll treat it as a general Exp galaxy. sources = [] for c in catalog: # Create a "position" object given the catalog RA,Dec position = RaDecPos(c.ra, c.dec) # Create a "brightness" object; in the catalog, the fluxes are # stored in a [ugrizY] array, so pull out the right index band_index = 'ugrizY'.index(band) flux = c.decam_flux[band_index] brightness = NanoMaggies(**{band: flux}) # Depending on the source classification in the catalog, pull # out different fields for the galaxy shape, and for the # galaxy type. The DECaLS catalogs, conveniently, store # galaxy shapes as (radius, e1, e2) ellipses. if c.type.strip() == 'DEV': shape = EllipseE(c.shapedev_r, c.shapedev_e1, c.shapedev_e2) galclass = DevGalaxy elif c.type.strip() == 'SIMP': shape = EllipseE(c.shapeexp_r, c.shapeexp_e1, c.shapeexp_e2) galclass = ExpGalaxy else: assert (False) # Create the tractor galaxy object source = galclass(position, brightness, shape) print('Created', source) sources.append(source) # Create the Tractor object -- a list of tractor Images and a list of tractor sources. tractor = Tractor([tim], sources) # Render the initial model image. print('Getting initial model...') mod = tractor.getModelImage(0) make_plot(tim, mod, 'Initial Scene', 'mod0.png') # Instantiate a new source at the location of the unmodelled peak. print('Adding new source...') # Find the peak very naively... ipeak = np.argmax((image - mod) * tim.inverr) iy, ix = np.unravel_index(ipeak, tim.shape) print('Residual peak at', ix, iy) # Compute the RA,Dec location of the peak... radec = tim.getWcs().pixelToPosition(ix, iy) print('RA,Dec', radec) # Try modelling it as a point source. # We'll initialize the brightness arbitrarily to 1 nanomaggy (= mag 22.5) brightness = NanoMaggies(**{band: 1.}) source = PointSource(radec, brightness) # Add it to the catalog! tractor.catalog.append(source) # Render the new model image with this source added. mod = tractor.getModelImage(0) make_plot(tim, mod, 'New Source (Before Fit)', 'mod1.png') print('Fitting new source...') # Now we're going to fit for the properties of the new source we # added. # We don't want to fit for any of the image calibration properties: tractor.freezeParam('images') # And we don't (yet) want to fit the existing sources. The new # source is index number 2, so freeze everything else in the catalog. tractor.catalog.freezeAllBut(2) print('Fitting parameters:') tractor.printThawedParams() # Do the actual optimization: tractor.optimize_loop() mod = tractor.getModelImage(0) make_plot(tim, mod, 'New Source Fit', 'mod2.png') print('Fitting sources simultaneously...') # Now let's unfreeze all the sources and fit them simultaneously. tractor.catalog.thawAllParams() tractor.printThawedParams() tractor.optimize_loop() mod = tractor.getModelImage(0) make_plot(tim, mod, 'Simultaneous Fit', 'mod3.png')
def scan_dchisq(seeing, target_dchisq, ps, e1=0.): pixscale = 0.262 psfsigma = seeing / pixscale / 2.35 print('PSF sigma:', psfsigma, 'pixels') psf = GaussianMixturePSF(1., 0., 0., psfsigma**2, psfsigma**2, 0.) sig1 = 0.01 psfnorm = 1./(2. * np.sqrt(np.pi) * psfsigma) detsig1 = sig1 / psfnorm sz = 50 cd = pixscale / 3600. wcs = Tan(0., 0., float(sz/2), float(sz/2), -cd, 0., 0., cd, float(sz), float(sz)) band = 'r' tim = Image(data=np.zeros((sz,sz)), inverr=np.ones((sz,sz)) / sig1, psf=psf, wcs = ConstantFitsWcs(wcs), photocal = LinearPhotoCal(1., band=band)) re_vals = np.logspace(-1., 0., 50) all_runs = [] mods = [] for i,re in enumerate(re_vals): true_src = ExpGalaxy(RaDecPos(0., 0.), NanoMaggies(**{band: 1.}), EllipseE(re, e1, 0.)) print('True source:', true_src) tr = Tractor([tim], [true_src]) tr.freezeParams('images') true_mod = tr.getModelImage(0) dchisq_none = np.sum((true_mod * tim.inverr)**2) scale = np.sqrt(target_dchisq / dchisq_none) true_src.brightness.setParams([scale]) true_mod = tr.getModelImage(0) dchisq_none = np.sum((true_mod * tim.inverr)**2) mods.append(true_mod) tim.data = true_mod exp_src = true_src.copy() psf_src = PointSource(true_src.pos.copy(), true_src.brightness.copy()) simp_src = SimpleGalaxy(true_src.pos.copy(), true_src.brightness.copy()) dchisqs = [] #for src in [psf_src, simp_src, exp_src]: for src in [psf_src, simp_src]: src.freezeParam('pos') #print('Fitting source:', src) #src.printThawedParams() tr.catalog[0] = src tr.optimize_loop() #print('Fitted:', src) mod = tr.getModelImage(0) dchisqs.append(dchisq_none - np.sum(((true_mod - mod) * tim.inverr)**2)) #print('dchisq:', dchisqs[-1]) dchisqs.append(dchisq_none) all_runs.append([re,] + dchisqs) all_runs = np.array(all_runs) re = all_runs[:,0] dchi_psf = all_runs[:,1] dchi_simp = all_runs[:,2] dchi_exp = all_runs[:,3] dchi_ps = np.maximum(dchi_psf, dchi_simp) dchi_cut1 = dchi_ps + 3+9 dchi_cut2 = dchi_ps + dchi_psf * 0.02 dchi_cut3 = dchi_ps + dchi_psf * 0.008 plt.clf() plt.plot(re, dchi_psf, 'k-', label='PSF') plt.plot(re, dchi_simp, 'b-', label='SIMP') plt.plot(re, dchi_exp, 'r-', label='EXP') plt.plot(re, dchi_cut2, 'm--', alpha=0.5, lw=2, label='Cut: 2%') plt.plot(re, dchi_cut3, 'm:', alpha=0.5, lw=2, label='Cut: 0.08%') plt.plot(re, dchi_cut1, 'm-', alpha=0.5, lw=2, label='Cut: 12') plt.xlabel('True r_e (arcsec)') plt.ylabel('dchisq') #plt.legend(loc='lower left') plt.legend(loc='upper right') tt = 'Seeing = %g arcsec, S/N ~ %i' % (seeing, int(np.round(np.sqrt(target_dchisq)))) if e1 != 0.: tt += ', Ellipticity %g' % e1 plt.title(tt) plt.ylim(0.90 * target_dchisq, 1.05 * target_dchisq) # aspect = 1.2 # ax = plt.axis() # dre = (ax[1]-ax[0]) / 20 / aspect # dchi = (ax[3]-ax[2]) / 20 # I = np.linspace(0, len(re_vals)-1, 8).astype(int) # for mod,re in [(mods[i], re_vals[i]) for i in I]: # print('extent:', [re-dre, re+dre, ax[2], ax[2]+dchi]) # plt.imshow(mod, interpolation='nearest', origin='lower', aspect='auto', # extent=[re-dre, re+dre, ax[2], ax[2]+dchi], cmap='gray') # plt.axis(ax) ps.savefig()