def test_io(self): # Write PsfExModel to disk, then read and check consistency. import tempfile f, tmpfn = tempfile.mkstemp(suffix='.fits') os.close(f) self.psf.psfex.writeto(tmpfn) print('Wrote', tmpfn) # Read mod = PsfExModel(fn=tmpfn) psf = PixelizedPsfEx(None, psfex=mod) orig_im = self.psf.getImage(0., 0.) im = psf.getImage(0., 0.) self.assertEqual(np.max(np.abs(orig_im - im)), 0.) os.unlink(tmpfn)
def read_psf_model(self, x0, y0, gaussPsf=False, pixPsf=False, const2psf=False, psf_sigma=1., cx=0, cy=0): psffn = None if gaussPsf: from tractor.basics import GaussianMixturePSF v = psf_sigma**2 psf = GaussianMixturePSF(1., 0., 0., v, v, 0.) print('WARNING: using mock PSF:', psf) psf.version = '0' psf.plver = '' elif pixPsf: # spatially varying pixelized PsfEx from tractor.psfex import PixelizedPsfEx print('Reading PsfEx model from', self.psffn) psf = PixelizedPsfEx(self.psffn) psf.shift(x0, y0) psffn = self.psffn elif const2psf: from tractor.psfex import PsfExModel from tractor.basics import GaussianMixtureEllipsePSF # 2-component constant MoG. print('Reading PsfEx model from', self.psffn) psffn = self.psffn psfex = PsfExModel(self.psffn) psfim = psfex.at(cx, cy) psfim = psfim[5:-5, 5:-5] print('Fitting PsfEx model as 2-component Gaussian...') psf = GaussianMixtureEllipsePSF.fromStamp(psfim, N=2) del psfim del psfex else: assert (False) print('Using PSF model', psf) if psffn is not None: hdr = fitsio.read_header(psffn) psf.version = hdr.get('LEGSURV', None) if psf.version is None: psf.version = str(os.stat(psffn).st_mtime) psf.plver = hdr.get('PLVER', '').strip() return psf
def read_psf_model(self, x0, y0, gaussPsf=False, pixPsf=False, hybridPsf=False, psf_sigma=1., cx=0, cy=0): assert (gaussPsf or pixPsf or hybridPsf) psffn = None if gaussPsf: from tractor.basics import GaussianMixturePSF v = psf_sigma**2 psf = GaussianMixturePSF(1., 0., 0., v, v, 0.) print('WARNING: using mock PSF:', psf) psf.version = '0' psf.plver = '' else: # spatially varying pixelized PsfEx from tractor.psfex import PixelizedPsfEx print('Reading PsfEx model from', self.psffn) psf = PixelizedPsfEx(self.psffn) psf.shift(x0, y0) psffn = self.psffn if hybridPsf: from tractor.psf import HybridPixelizedPSF psf = HybridPixelizedPSF(psf) print('Using PSF model', psf) if psffn is not None: hdr = fitsio.read_header(psffn) psf.version = hdr.get('LEGSURV', None) if psf.version is None: psf.version = str(os.stat(psffn).st_mtime) psf.plver = hdr.get('PLVER', '').strip() return psf
def psf_residuals(expnum, ccdname, stampsize=35, nstar=30, magrange=(13, 17), verbose=0, splinesky=False): # Set the debugging level. if verbose == 0: lvl = logging.INFO else: lvl = logging.DEBUG logging.basicConfig(level=lvl, format='%(message)s', stream=sys.stdout) pngprefix = 'qapsf-{}-{}'.format(expnum, ccdname) # Gather all the info we need about this CCD. decals = Decals() ccd = decals.find_ccds(expnum=expnum, ccdname=ccdname)[0] band = ccd.filter ps1band = dict(g=0, r=1, i=2, z=3, Y=4) print('Band {}'.format(band)) #scales = dict(g=0.0066, r=0.01, z=0.025) #vmin, vmax = np.arcsinh(-1), np.arcsinh(100) #print(scales[band]) im = decals.get_image_object(ccd) iminfo = im.get_image_info() H, W = iminfo['dims'] wcs = im.get_wcs() # Choose a uniformly selected subset of PS1 stars on this CCD. ps1 = ps1cat(ccdwcs=wcs) cat = ps1.get_stars(band=band, magrange=magrange) rand = np.random.RandomState(seed=expnum * ccd.ccdnum) these = rand.choice(len(cat) - 1, nstar, replace=False) #these = rand.random_integers(0,len(cat)-1,nstar) cat = cat[these] cat = cat[np.argsort(cat.median[:, ps1band[band]])] # sort by magnitude #print(cat.nmag_ok) get_tim_kwargs = dict(const2psf=True, splinesky=splinesky) # Make a QAplot of the positions of all the stars. tim = im.get_tractor_image(**get_tim_kwargs) img = tim.getImage() #img = tim.getImage()/scales[band] fig = plt.figure(figsize=(5, 10)) ax = fig.gca() ax.get_xaxis().get_major_formatter().set_useOffset(False) #ax.imshow(np.arcsinh(img),cmap='gray',interpolation='nearest', # origin='lower',vmin=vmax,vmax=vmax) ax.imshow(img, **tim.ima) ax.axis('off') ax.set_title('{}: {}/{} AM={:.2f} Seeing={:.3f}"'.format( band, expnum, ccdname, ccd.airmass, ccd.seeing)) for istar, ps1star in enumerate(cat): ra, dec = (ps1star.ra, ps1star.dec) ok, xpos, ypos = wcs.radec2pixelxy(ra, dec) ax.text(xpos, ypos, '{:2d}'.format(istar + 1), color='red', horizontalalignment='left') circ = plt.Circle((xpos, ypos), radius=30, color='g', fill=False, lw=1) ax.add_patch(circ) #radec = wcs.radec_bounds() #ax.scatter(cat.ra,cat.dec) #ax.set_xlim([radec[1],radec[0]])#*[1.0002,0.9998]) #ax.set_ylim([radec[2],radec[3]])#*[0.985,1.015]) #ax.set_xlabel('$RA\ (deg)$',fontsize=18) #ax.set_ylabel('$Dec\ (deg)$',fontsize=18) fig.savefig(pngprefix + '-ccd.png', bbox_inches='tight') # Initialize the many-stamp QAplot ncols = 3 nrows = np.ceil(nstar / ncols).astype('int') inchperstamp = 2.0 fig = plt.figure(figsize=(inchperstamp * 3 * ncols, inchperstamp * nrows)) irow = 0 icol = 0 for istar, ps1star in enumerate(cat): ra, dec = (ps1star.ra, ps1star.dec) mag = ps1star.median[ps1band[band]] # r-band ok, xpos, ypos = wcs.radec2pixelxy(ra, dec) ix, iy = int(xpos), int(ypos) # create a little tractor Image object around the star slc = (slice(max(iy - stampsize, 0), min(iy + stampsize + 1, H)), slice(max(ix - stampsize, 0), min(ix + stampsize + 1, W))) # The PSF model 'const2Psf' is the one used in DR1: a 2-component # Gaussian fit to PsfEx instantiated in the image center. tim = im.get_tractor_image(slc=slc, **get_tim_kwargs) stamp = tim.getImage() ivarstamp = tim.getInvvar() # Initialize a tractor PointSource from PS1 measurements flux = NanoMaggies.magToNanomaggies(mag) star = PointSource(RaDecPos(ra, dec), NanoMaggies(**{band: flux})) # Fit just the source RA,Dec,flux. tractor = Tractor([tim], [star]) tractor.freezeParam('images') print('2-component MOG:', tim.psf) tractor.printThawedParams() for step in range(50): dlnp, X, alpha = tractor.optimize() if dlnp < 0.1: break print('Fit:', star) model_mog = tractor.getModelImage(0) chi2_mog = -2.0 * tractor.getLogLikelihood() mag_mog = NanoMaggies.nanomaggiesToMag(star.brightness)[0] # Now change the PSF model to a pixelized PSF model from PsfEx instantiated # at this place in the image. psf = PixelizedPsfEx(im.psffn) tim.psf = psf.constantPsfAt(xpos, ypos) #print('PSF model:', tim.psf) #tractor.printThawedParams() for step in range(50): dlnp, X, alpha = tractor.optimize() if dlnp < 0.1: break print('Fit:', star) model_psfex = tractor.getModelImage(0) chi2_psfex = -2.0 * tractor.getLogLikelihood() mag_psfex = NanoMaggies.nanomaggiesToMag(star.brightness)[0] #mn, mx = np.percentile((stamp-model_psfex)[ivarstamp>0],[1,95]) sig = np.std((stamp - model_psfex)[ivarstamp > 0]) mn, mx = [-2.0 * sig, 5 * sig] # Generate a QAplot. if (istar > 0) and (istar % (ncols) == 0): irow = irow + 1 icol = 3 * istar - 3 * ncols * irow #print(istar, irow, icol, icol+1, icol+2) ax1 = plt.subplot2grid((nrows, 3 * ncols), (irow, icol), aspect='equal') ax1.axis('off') #ax1.imshow(stamp, **tim.ima) ax1.imshow(stamp, cmap='gray', interpolation='nearest', origin='lower', vmin=mn, vmax=mx) ax1.text(0.1, 0.9, '{:2d}'.format(istar + 1), color='white', horizontalalignment='left', verticalalignment='top', transform=ax1.transAxes) ax2 = plt.subplot2grid((nrows, 3 * ncols), (irow, icol + 1), aspect='equal') ax2.axis('off') #ax2.imshow(stamp-model_mog, **tim.ima) ax2.imshow(stamp - model_mog, cmap='gray', interpolation='nearest', origin='lower', vmin=mn, vmax=mx) ax2.text(0.1, 0.9, 'MoG', color='white', horizontalalignment='left', verticalalignment='top', transform=ax2.transAxes) ax2.text(0.08, 0.08, '{:.3f}'.format(mag_mog), color='white', horizontalalignment='left', verticalalignment='bottom', transform=ax2.transAxes) #ax2.set_title('{:.3f}, {:.2f}'.format(mag_psfex,chi2_psfex),fontsize=14) #ax2.set_title('{:.3f}, $\chi^{2}$={:.2f}'.format(mag_psfex,chi2_psfex)) ax3 = plt.subplot2grid((nrows, 3 * ncols), (irow, icol + 2), aspect='equal') ax3.axis('off') #ax3.imshow(stamp-model_psfex, **tim.ima) ax3.imshow(stamp - model_psfex, cmap='gray', interpolation='nearest', origin='lower', vmin=mn, vmax=mx) ax3.text(0.1, 0.9, 'PSFEx', color='white', horizontalalignment='left', verticalalignment='top', transform=ax3.transAxes) ax3.text(0.08, 0.08, '{:.3f}'.format(mag_psfex), color='white', horizontalalignment='left', verticalalignment='bottom', transform=ax3.transAxes) if istar == (nstar - 1): break fig.savefig(pngprefix + '-stargrid.png', bbox_inches='tight')
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 psf(self, ext): path = os.path.join(self.sxdir, self.psf_name) return PixelizedPsfEx(path)
def psf_residuals(expnum,ccdname,stampsize=35,nstar=30, magrange=(13,17),verbose=0): # Set the debugging level. if verbose==0: lvl = logging.INFO else: lvl = logging.DEBUG logging.basicConfig(level=lvl,format='%(message)s',stream=sys.stdout) pngprefix = 'qapsf-{}-{}'.format(expnum,ccdname) # Gather all the info we need about this CCD. decals = Decals() ccd = decals.find_ccds(expnum=expnum,ccdname=ccdname)[0] band = ccd.filter ps1band = dict(g=0,r=1,i=2,z=3,Y=4) print('Band {}'.format(band)) #scales = dict(g=0.0066, r=0.01, z=0.025) #vmin, vmax = np.arcsinh(-1), np.arcsinh(100) #print(scales[band]) im = DecamImage(decals,ccd) iminfo = im.get_image_info() H,W = iminfo['dims'] wcs = im.get_wcs() # Choose a uniformly selected subset of PS1 stars on this CCD. ps1 = ps1cat(ccdwcs=wcs) cat = ps1.get_stars(band=band,magrange=magrange) rand = np.random.RandomState(seed=expnum*ccd.ccdnum) these = rand.choice(len(cat)-1,nstar,replace=False) #these = rand.random_integers(0,len(cat)-1,nstar) cat = cat[these] cat = cat[np.argsort(cat.median[:,ps1band[band]])] # sort by magnitude #print(cat.nmag_ok) # Make a QAplot of the positions of all the stars. tim = im.get_tractor_image(const2psf=True) img = tim.getImage() #img = tim.getImage()/scales[band] fig = plt.figure(figsize=(5,10)) ax = fig.gca() ax.get_xaxis().get_major_formatter().set_useOffset(False) #ax.imshow(np.arcsinh(img),cmap='gray',interpolation='nearest', # origin='lower',vmin=vmax,vmax=vmax) ax.imshow(img, **tim.ima) ax.axis('off') ax.set_title('{}: {}/{} AM={:.2f} Seeing={:.3f}"'. format(band,expnum,ccdname,ccd.airmass,ccd.seeing)) for istar, ps1star in enumerate(cat): ra, dec = (ps1star.ra, ps1star.dec) ok, xpos, ypos = wcs.radec2pixelxy(ra, dec) ax.text(xpos,ypos,'{:2d}'.format(istar+1),color='red', horizontalalignment='left') circ = plt.Circle((xpos,ypos),radius=30,color='g',fill=False,lw=1) ax.add_patch(circ) #radec = wcs.radec_bounds() #ax.scatter(cat.ra,cat.dec) #ax.set_xlim([radec[1],radec[0]])#*[1.0002,0.9998]) #ax.set_ylim([radec[2],radec[3]])#*[0.985,1.015]) #ax.set_xlabel('$RA\ (deg)$',fontsize=18) #ax.set_ylabel('$Dec\ (deg)$',fontsize=18) fig.savefig(pngprefix+'-ccd.png',bbox_inches='tight') # Initialize the many-stamp QAplot ncols = 3 nrows = np.ceil(nstar/ncols).astype('int') inchperstamp = 2.0 fig = plt.figure(figsize=(inchperstamp*3*ncols,inchperstamp*nrows)) irow = 0 icol = 0 for istar, ps1star in enumerate(cat): ra, dec = (ps1star.ra, ps1star.dec) mag = ps1star.median[ps1band[band]] # r-band ok, xpos, ypos = wcs.radec2pixelxy(ra, dec) ix,iy = int(xpos), int(ypos) # create a little tractor Image object around the star slc = (slice(max(iy-stampsize, 0), min(iy+stampsize+1, H)), slice(max(ix-stampsize, 0), min(ix+stampsize+1, W))) # The PSF model 'const2Psf' is the one used in DR1: a 2-component # Gaussian fit to PsfEx instantiated in the image center. tim = im.get_tractor_image(slc=slc, const2psf=True) stamp = tim.getImage() ivarstamp = tim.getInvvar() # Initialize a tractor PointSource from PS1 measurements flux = NanoMaggies.magToNanomaggies(mag) star = PointSource(RaDecPos(ra,dec), NanoMaggies(**{band: flux})) # Fit just the source RA,Dec,flux. tractor = Tractor([tim], [star]) tractor.freezeParam('images') print('2-component MOG:', tim.psf) tractor.printThawedParams() for step in range(50): dlnp,X,alpha = tractor.optimize() if dlnp < 0.1: break print('Fit:', star) model_mog = tractor.getModelImage(0) chi2_mog = -2.0*tractor.getLogLikelihood() mag_mog = NanoMaggies.nanomaggiesToMag(star.brightness)[0] # Now change the PSF model to a pixelized PSF model from PsfEx instantiated # at this place in the image. psf = PixelizedPsfEx(im.psffn) tim.psf = psf.constantPsfAt(xpos, ypos) #print('PSF model:', tim.psf) #tractor.printThawedParams() for step in range(50): dlnp,X,alpha = tractor.optimize() if dlnp < 0.1: break print('Fit:', star) model_psfex = tractor.getModelImage(0) chi2_psfex = -2.0*tractor.getLogLikelihood() mag_psfex = NanoMaggies.nanomaggiesToMag(star.brightness)[0] #mn, mx = np.percentile((stamp-model_psfex)[ivarstamp>0],[1,95]) sig = np.std((stamp-model_psfex)[ivarstamp>0]) mn, mx = [-2.0*sig,5*sig] # Generate a QAplot. if (istar>0) and (istar%(ncols)==0): irow = irow+1 icol = 3*istar - 3*ncols*irow #print(istar, irow, icol, icol+1, icol+2) ax1 = plt.subplot2grid((nrows,3*ncols), (irow,icol), aspect='equal') ax1.axis('off') #ax1.imshow(stamp, **tim.ima) ax1.imshow(stamp,cmap='gray',interpolation='nearest', origin='lower',vmin=mn,vmax=mx) ax1.text(0.1,0.9,'{:2d}'.format(istar+1),color='white', horizontalalignment='left',verticalalignment='top', transform=ax1.transAxes) ax2 = plt.subplot2grid((nrows,3*ncols), (irow,icol+1), aspect='equal') ax2.axis('off') #ax2.imshow(stamp-model_mog, **tim.ima) ax2.imshow(stamp-model_mog,cmap='gray',interpolation='nearest', origin='lower',vmin=mn,vmax=mx) ax2.text(0.1,0.9,'MoG',color='white', horizontalalignment='left',verticalalignment='top', transform=ax2.transAxes) ax2.text(0.08,0.08,'{:.3f}'.format(mag_mog),color='white', horizontalalignment='left',verticalalignment='bottom', transform=ax2.transAxes) #ax2.set_title('{:.3f}, {:.2f}'.format(mag_psfex,chi2_psfex),fontsize=14) #ax2.set_title('{:.3f}, $\chi^{2}$={:.2f}'.format(mag_psfex,chi2_psfex)) ax3 = plt.subplot2grid((nrows,3*ncols), (irow,icol+2), aspect='equal') ax3.axis('off') #ax3.imshow(stamp-model_psfex, **tim.ima) ax3.imshow(stamp-model_psfex,cmap='gray',interpolation='nearest', origin='lower',vmin=mn,vmax=mx) ax3.text(0.1,0.9,'PSFEx',color='white', horizontalalignment='left',verticalalignment='top', transform=ax3.transAxes) ax3.text(0.08,0.08,'{:.3f}'.format(mag_psfex),color='white', horizontalalignment='left',verticalalignment='bottom', transform=ax3.transAxes) if istar==(nstar-1): break fig.savefig(pngprefix+'-stargrid.png',bbox_inches='tight')
def setUp(self): fn = os.path.join(os.path.dirname(__file__), 'psfex-decam-00392360-S31.fits') self.psf = PixelizedPsfEx(fn)
class PsfExTest(unittest.TestCase): def setUp(self): fn = os.path.join(os.path.dirname(__file__), 'psfex-decam-00392360-S31.fits') self.psf = PixelizedPsfEx(fn) 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) # psf.getImage(px,py) def test_shifted(self): # Test that the PsfEx model varies spatially # Test that the PixelizedPsfEx.getShifted() method works # (shifted model's model at 0.,0. equals the original at dx,dy) dx, dy = 50., 100. psfim0 = self.psf.getImage(0., 0.) psfim = self.psf.getImage(dx, dy) # spatial variation rms = np.sqrt(np.mean((psfim0 - psfim)**2)) self.assertTrue((rms > 0) * (rms < 1e-4)) # getShifted() subpsf = self.psf.getShifted(dx, dy) subim = subpsf.getImage(0., 0.) rms = np.sqrt(np.mean((subim - psfim)**2)) self.assertEqual(rms, 0.) def test_io(self): # Write PsfExModel to disk, then read and check consistency. import tempfile f, tmpfn = tempfile.mkstemp(suffix='.fits') os.close(f) self.psf.psfex.writeto(tmpfn) print('Wrote', tmpfn) # Read mod = PsfExModel(fn=tmpfn) psf = PixelizedPsfEx(None, psfex=mod) orig_im = self.psf.getImage(0., 0.) im = psf.getImage(0., 0.) self.assertEqual(np.max(np.abs(orig_im - im)), 0.) os.unlink(tmpfn) def test_fourier(self): F, (cx, cy), shape, (v, w) = self.psf.getFourierTransform(100., 100., 32) print('F', F) print('cx,cy', cx, cy) print('shape', shape) print('v, w', v, w) if ps is not None: import pylab as plt from astrometry.util.plotutils import dimshow plt.clf() plt.subplot(1, 2, 1) dimshow(F.real) plt.subplot(1, 2, 2) dimshow(F.imag) ps.savefig() def test_psfex(self): if ps is not None: from astrometry.util.plotutils import dimshow import pylab as plt H, W = 100, 100 cx, cy = W / 2., H / 2. pixpsf = self.psf.constantPsfAt(cx, cy) ph, pw = pixpsf.shape xx, yy = np.meshgrid(np.arange(pw), np.arange(ph)) im = pixpsf.img.copy() im /= np.sum(im) cenx, ceny = np.sum(im * xx), np.sum(im * yy) print('Pixpsf centroid:', cenx, ceny) print('shape:', ph, pw) dx, dy = cenx - pw // 2, ceny - ph // 2 print('dx,dy', dx, dy) # gpsf = GaussianMixturePSF.fromStamp(im, N=1) # print('Fit gpsf:', gpsf) # self.assertTrue(np.abs(gpsf.mog.mean[0,0] - dx) < 0.1) # self.assertTrue(np.abs(gpsf.mog.mean[0,1] - dy) < 0.1) # self.assertTrue(np.abs(gpsf.mog.var[0,0,0] - 15.5) < 1.) # self.assertTrue(np.abs(gpsf.mog.var[0,1,1] - 13.5) < 1.) # self.assertTrue(np.abs(gpsf.mog.var[0,1,0] - -1) < 1.) gpsf = GaussianMixturePSF.fromStamp(im, N=2) print('Fit gpsf:', gpsf) print('Params:', ', '.join(['%.1f' % p for p in gpsf.getParams()])) pp = np.array( [0.8, 0.2, 0.1, -0.0, 1.2, 0.2, 7.6, 6.0, -1.0, 51.6, 49.1, -1.3]) self.assertTrue(np.all(np.abs(np.array(gpsf.getParams()) - pp) < 0.1)) tim = Image(data=np.zeros((H, W)), invvar=np.ones((H, W)), psf=self.psf) xx, yy = np.meshgrid(np.arange(W), np.arange(H)) star = PointSource(PixPos(cx, cy), Flux(100.)) gal = ExpGalaxy(PixPos(cx, cy), Flux(100.), EllipseE(1., 0., 0.)) tr1 = Tractor([tim], [star]) tr2 = Tractor([tim], [gal]) disable_galaxy_cache() tim.psf = self.psf mod = tr1.getModelImage(0) mod1 = mod im = mod.copy() im /= im.sum() cenx, ceny = np.sum(im * xx), np.sum(im * yy) print('Star model + PsfEx centroid', cenx, ceny) self.assertTrue(np.abs(cenx - (cx + dx)) < 0.1) self.assertTrue(np.abs(ceny - (cy + dy)) < 0.1) if ps is not None: plt.clf() dimshow(mod) plt.title('Star model, PsfEx') ps.savefig() tim.psf = pixpsf mod = tr1.getModelImage(0) if ps is not None: plt.clf() dimshow(mod) plt.title('Star model, pixpsf') ps.savefig() tim.psf = gpsf mod = tr1.getModelImage(0) mod2 = mod if ps is not None: plt.clf() dimshow(mod) plt.title('Star model, gpsf') plt.colorbar() ps.savefig() plt.clf() dimshow(mod1 - mod2) plt.title('Star model, PsfEx - gpsf') plt.colorbar() ps.savefig() # range ~ -0.15 to +0.25 self.assertTrue(np.all(np.abs(mod1 - mod2) < 0.25)) tim.psf = self.psf mod = tr2.getModelImage(0) mod1 = mod im = mod.copy() im /= im.sum() cenx, ceny = np.sum(im * xx), np.sum(im * yy) print('Gal model + PsfEx centroid', cenx, ceny) self.assertTrue(np.abs(cenx - (cx + dx)) < 0.1) self.assertTrue(np.abs(ceny - (cy + dy)) < 0.1) if ps is not None: plt.clf() dimshow(mod) plt.title('Gal model, PsfEx') ps.savefig() # tim.psf = pixpsf # mod = tr2.getModelImage(0) # plt.clf() # dimshow(mod) # plt.title('Gal model, pixpsf') # ps.savefig() tim.psf = gpsf mod = tr2.getModelImage(0) mod2 = mod # range ~ -0.1 to +0.2 self.assertTrue(np.all(np.abs(mod1 - mod2) < 0.2)) if ps is not None: plt.clf() dimshow(mod) plt.title('Gal model, gpsf') ps.savefig() plt.clf() dimshow(mod1 - mod2) plt.title('Gal model, PsfEx - gpsf') plt.colorbar() ps.savefig()