def test(self): schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("ccd", int, doc="CCD number") schema.addField("visit", long, doc="Visit number") schema.addField("goodpix", int, doc="Number of good pixels") schema.addField("weight", float, doc="Weighting for this CCD") ccds = afwTable.ExposureCatalog(schema) wcs = afwImage.makeWcs(afwCoord.Coord(0.0*afwGeom.degrees, 0.0*afwGeom.degrees), afwGeom.Point2D(0.0, 0.0), 1.0e-4, 0.0, 0.0, 1.0e-4) new = ccds.addNew() new.set("id", 0) new.set("bbox.min", afwGeom.Point2I(0,0)) new.set("bbox.max", afwGeom.Point2I(1024,1024)) # The following lines are critical for reproducing the bug, because # the code is reading a double starting at the 'ccd' (offset 24), and # it sees a zero (from the zero in 'ccd' and the leading zeros in 'visit'). new.set("ccd", 0) new.set("visit", 6789) new.set("goodpix", 987654321) new.set("weight", 1.0) new.setPsf(measAlg.SingleGaussianPsf(23, 23, 2.345)) new.setWcs(wcs) # In the presence of the bug, the following fails with: # LsstCppException: 0: lsst::pex::exceptions::RuntimeErrorException thrown at src/CoaddPsf.cc:134 in lsst::afw::geom::Point2D lsst::meas::algorithms::{anonymous}::computeAveragePosition(const ExposureCatalog&, const lsst::afw::image::Wcs&, lsst::afw::table::Key<double>) # 0: Message: Could not find a valid average position for CoaddPsf measAlg.CoaddPsf(ccds, wcs)
def testTicket2872(self): """Test that CoaddPsf.getAveragePosition() is always a position at which we can call computeImage(). """ schema = afwTable.ExposureTable.makeMinimalSchema() weightKey = schema.addField("weight", type=float, doc="photometric weight") catalog = afwTable.ExposureCatalog(schema) cdelt = (0.2 * afwGeom.arcseconds).asDegrees() wcs = afwImage.makeWcs( afwCoord.IcrsCoord(afwGeom.Point2D(45.0, 45.0), afwGeom.degrees), afwGeom.Point2D(50, 50), cdelt, 0.0, 0.0, cdelt) kernel = measAlg.DoubleGaussianPsf(7, 7, 2.0).getKernel() psf1 = measAlg.KernelPsf(kernel, afwGeom.Point2D(0, 50)) psf2 = measAlg.KernelPsf(kernel, afwGeom.Point2D(100, 50)) record1 = catalog.addNew() record1.setPsf(psf1) record1.setWcs(wcs) record1.setD(weightKey, 1.0) record1.setBBox( afwGeom.Box2I(afwGeom.Point2I(-40, 0), afwGeom.Point2I(40, 100))) record2 = catalog.addNew() record2.setPsf(psf2) record2.setWcs(wcs) record2.setD(weightKey, 1.0) record2.setBBox( afwGeom.Box2I(afwGeom.Point2I(60, 0), afwGeom.Point2I(140, 100))) coaddPsf = measAlg.CoaddPsf(catalog, wcs) naiveAvgPos = afwGeom.Point2D(50, 50) self.assertRaises(pexExceptions.InvalidParameterError, coaddPsf.computeKernelImage, naiveAvgPos) # important test is that this doesn't throw: coaddPsf.computeKernelImage()
def test(self): schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("ccd", np.int32, doc="CCD number") schema.addField("visit", np.int32, doc="Visit number") schema.addField("goodpix", np.int32, doc="Number of good pixels") schema.addField("weight", float, doc="Weighting for this CCD") ccds = afwTable.ExposureCatalog(schema) wcs = afwImage.makeWcs(afwCoord.Coord(0.0*afwGeom.degrees, 0.0*afwGeom.degrees), afwGeom.Point2D(0.0, 0.0), 1.0e-4, 0.0, 0.0, 1.0e-4) new = ccds.addNew() new.set("id", 0) new.set("bbox_min_x", 0) new.set("bbox_min_y", 0) new.set("bbox_max_x", 1024) new.set("bbox_max_y", 1024) # The following lines are critical for reproducing the bug, because # the code is reading a double starting at the 'ccd' (offset 24), and # it sees a zero (from the zero in 'ccd' and the leading zeros in 'visit'). new.set("ccd", 0) new.set("visit", 6789) new.set("goodpix", 987654321) new.set("weight", 1.0) new.setPsf(measAlg.SingleGaussianPsf(23, 23, 2.345)) new.setWcs(wcs) # In the presence of the bug, the following fails with # lsst::pex::exceptions::RuntimeError thrown in src/CoaddPsf.cc # with message: "Could not find a valid average position for CoaddPsf" measAlg.CoaddPsf(ccds, wcs)
def runQuantum(self, butlerQC, inputRefs, outputRefs): visit = butlerQC.quantum.dataId['visit'] schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField('visit', type='L', doc='visit number') metadata = dafBase.PropertyList() metadata.add("COMMENT", "Catalog id is detector id, sorted") metadata.add("COMMENT", "Only detectors with data have entries") photoCalibCat = afwTable.ExposureCatalog(schema) photoCalibCat.setMetadata(metadata) photoCalibCat.reserve(len(inputRefs.photoCalibList)) photoCalibList = butlerQC.get(inputRefs.photoCalibList) for dataRef in photoCalibList: detector = dataRef.dataId['detector'] photoCalib = dataRef.get() rec = photoCalibCat.addNew() rec['id'] = detector rec['visit'] = visit rec.setPhotoCalib(photoCalib) photoCalibCat.sort() butlerQC.put(photoCalibCat, outputRefs.photoCalibGlobalCatalog)
def testValidPolygonPsf(self): """Test that we can use the validPolygon on exposures in the coadd psf""" print "ValidPolygonTest" # this is the coadd Wcs we want cd11 = 5.55555555e-05 cd12 = 0.0 cd21 = 0.0 cd22 = 5.55555555e-05 crval1 = 0.0 crval2 = 0.0 crpix = afwGeom.PointD(1000, 1000) crval = afwCoord.Coord(afwGeom.Point2D(crval1, crval2)) wcsref = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("weight", type="D", doc="Coadd weight") mycatalog = afwTable.ExposureCatalog(schema) # Each of the 9 has its peculiar Psf, Wcs, weight, bounding box, and valid region. for i in range(1, 10, 1): record = mycatalog.getTable().makeRecord() psf = measAlg.DoubleGaussianPsf(100, 100, i, 1.00, 0.0) record.setPsf(psf) crpix = afwGeom.PointD(1000 - 10.0 * i, 1000.0 - 10.0 * i) wcs = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) record.setWcs(wcs) record['weight'] = 1.0 * (i + 1) record['id'] = i bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1000, 1000)) record.setBBox(bbox) validPolygon_bbox = afwGeom.Box2D( afwGeom.Point2D(0, 0), afwGeom.Extent2D(i * 100, i * 100)) validPolygon = Polygon(validPolygon_bbox) record.setValidPolygon(validPolygon) mycatalog.append(record) # Create the coaddpsf and check at three different points to ensure that the validPolygon is working mypsf = measAlg.CoaddPsf(mycatalog, wcsref, 'weight') m1coadd, m2coadd = getCoaddSecondMoments(mypsf, afwGeom.Point2D(50, 50), True) m1, m2 = getPsfSecondMoments(mypsf, afwGeom.Point2D(50, 50)) self.assertTrue(testRelDiff(m1, m1coadd, .01)) self.assertTrue(testRelDiff(m2, m2coadd, .01)) m1coadd, m2coadd = getCoaddSecondMoments(mypsf, afwGeom.Point2D(500, 500), True) m1, m2 = getPsfSecondMoments(mypsf, afwGeom.Point2D(500, 500)) self.assertTrue(testRelDiff(m1, m1coadd, .01)) self.assertTrue(testRelDiff(m2, m2coadd, .01)) m1coadd, m2coadd = getCoaddSecondMoments(mypsf, afwGeom.Point2D(850, 850), True) m1, m2 = getPsfSecondMoments(mypsf, afwGeom.Point2D(850, 850)) self.assertTrue(testRelDiff(m1, m1coadd, .01)) self.assertTrue(testRelDiff(m2, m2coadd, .01))
def testCreate(self): """Check that we can create a CoaddPsf with 9 elements""" print "CreatePsfTest" # this is the coadd Wcs we want cd11 = 5.55555555e-05 cd12 = 0.0 cd21 = 0.0 cd22 = 5.55555555e-05 crval1 = 0.0 crval2 = 0.0 crpix = afwGeom.PointD(1000, 1000) crval = afwCoord.Coord(afwGeom.Point2D(crval1, crval2)) wcsref = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) #also test that the weight field name is correctly observed schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("customweightname", type="D", doc="Coadd weight") mycatalog = afwTable.ExposureCatalog(schema) # Each of the 9 has its peculiar Psf, Wcs, weight, and bounding box. for i in range(1, 10, 1): record = mycatalog.getTable().makeRecord() psf = measAlg.DoubleGaussianPsf(100, 100, i, 1.00, 0.0) record.setPsf(psf) crpix = afwGeom.PointD(i * 1000.0, i * 1000.0) wcs = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) record.setWcs(wcs) record['customweightname'] = 1.0 * (i + 1) record['id'] = i bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(i * 1000, i * 1000)) record.setBBox(bbox) mycatalog.append(record) #create the coaddpsf mypsf = measAlg.CoaddPsf(mycatalog, wcsref, 'customweightname') # check to be sure that we got the right number of components, in the right order self.assertTrue(mypsf.getComponentCount() == 9) for i in range(1, 10, 1): wcs = mypsf.getWcs(i - 1) psf = mypsf.getPsf(i - 1) bbox = mypsf.getBBox(i - 1) weight = mypsf.getWeight(i - 1) id = mypsf.getId(i - 1) self.assertTrue(i == id) self.assertTrue(weight == 1.0 * (i + 1)) self.assertTrue(bbox.getBeginX() == 0) self.assertTrue(bbox.getBeginY() == 0) self.assertTrue(bbox.getEndX() == 1000 * i) self.assertTrue(bbox.getEndY() == 1000 * i) self.assertTrue(wcs.getPixelOrigin().getX() == (1000.0 * i)) self.assertTrue(wcs.getPixelOrigin().getY() == (1000.0 * i)) m0, xbar, ybar, mxx, myy, x0, y0 = getPsfMoments( psf, afwGeom.Point2D(0, 0)) self.assertTrue(testRelDiff(i * i, mxx, .01)) self.assertTrue(testRelDiff(i * i, myy, .01))
def setUp(self): scale = 5.55555555e-05*lsst.geom.degrees self.cdMatrix = afwGeom.makeCdMatrix(scale=scale, flipX=True) self.crpix = lsst.geom.PointD(1000, 1000) self.crval = lsst.geom.SpherePoint(0.0, 0.0, lsst.geom.degrees) self.wcsref = afwGeom.makeSkyWcs(crpix=self.crpix, crval=self.crval, cdMatrix=self.cdMatrix) schema = afwTable.ExposureTable.makeMinimalSchema() self.weightKey = schema.addField("weight", type="D", doc="Coadd weight") self.mycatalog = afwTable.ExposureCatalog(schema)
def testExposureRecordPersistence(self): """Test that the ValidPolygon is saved with an ExposureRecord""" with lsst.utils.tests.getTempFilePath(".fits") as filename: cat1 = afwTable.ExposureCatalog( afwTable.ExposureTable.makeMinimalSchema()) record1 = cat1.addNew() record1.setValidPolygon(self.polygon) cat1.writeFits(filename) cat2 = afwTable.ExposureCatalog.readFits(filename) record2 = cat2[0] polygon2 = record2.getValidPolygon() self.assertEqual(self.polygon, polygon2)
def testExposureRecordPersistence(self): """Test that the ValidPolygon is saved with an ExposureRecord""" filename = "testValidPolygon.fits" cat1 = afwTable.ExposureCatalog(afwTable.ExposureTable.makeMinimalSchema()) record1 = cat1.addNew() record1.setValidPolygon(self.polygon) cat1.writeFits(filename) cat2 = afwTable.ExposureCatalog.readFits(filename) record2 = cat2[0] polygon2 = record2.getValidPolygon() self.assertEqual(self.polygon, polygon2) os.remove(filename)
def _constructPsf(self, mapperResults, exposure): """Construct a CoaddPsf based on PSFs from individual subExposures Currently uses (and returns) a CoaddPsf. TBD if we want to create a custom subclass of CoaddPsf to differentiate it. Parameters ---------- mapperResults : `list` list of `pipeBase.Struct` returned by `ImageMapper.run`. For this to work, each element of `mapperResults` must contain a `subExposure` element, from which the component Psfs are extracted (thus the reducerTask cannot have `reduceOperation = 'none'`. exposure : `lsst.afw.image.Exposure` the original exposure which is used here solely for its bounding-box and WCS. Returns ------- psf : `lsst.meas.algorithms.CoaddPsf` A psf constructed from the PSFs of the individual subExposures. """ schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("weight", type="D", doc="Coadd weight") mycatalog = afwTable.ExposureCatalog(schema) # We're just using the exposure's WCS (assuming that the subExposures' # WCSs are the same, which they better be!). wcsref = exposure.getWcs() for i, res in enumerate(mapperResults): record = mycatalog.getTable().makeRecord() if 'subExposure' in res.getDict(): subExp = res.subExposure if subExp.getWcs() != wcsref: raise ValueError( 'Wcs of subExposure is different from exposure') record.setPsf(subExp.getPsf()) record.setWcs(subExp.getWcs()) record.setBBox(subExp.getBBox()) elif 'psf' in res.getDict(): record.setPsf(res.psf) record.setWcs(wcsref) record.setBBox(res.bbox) record['weight'] = 1.0 record['id'] = i mycatalog.append(record) # create the coaddpsf psf = measAlg.CoaddPsf(mycatalog, wcsref, 'weight') return psf
def setUp(self): self.cd11 = 5.55555555e-05 self.cd12 = 0.0 self.cd21 = 0.0 self.cd22 = 5.55555555e-05 self.crval1 = 0.0 self.crval2 = 0.0 self.crpix = afwGeom.PointD(1000, 1000) self.crval = afwCoord.Coord(afwGeom.Point2D(self.crval1, self.crval2)) self.wcsref = afwImage.makeWcs(self.crval, self.crpix, self.cd11, self.cd12, self.cd21, self.cd22) schema = afwTable.ExposureTable.makeMinimalSchema() self.weightKey = schema.addField("weight", type="D", doc="Coadd weight") self.mycatalog = afwTable.ExposureCatalog(schema)
def testCreate(self): """Check that we can create a CoaddPsf with 9 elements.""" print("CreatePsfTest") # also test that the weight field name is correctly observed schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("customweightname", type="D", doc="Coadd weight") mycatalog = afwTable.ExposureCatalog(schema) # Each of the 9 has its peculiar Psf, Wcs, weight, and bounding box. for i in range(1, 10, 1): record = mycatalog.getTable().makeRecord() psf = measAlg.DoubleGaussianPsf(100, 100, i, 1.00, 0.0) record.setPsf(psf) crpix = lsst.geom.PointD(i * 1000.0, i * 1000.0) wcs = afwGeom.makeSkyWcs(crpix=crpix, crval=self.crval, cdMatrix=self.cdMatrix) record.setWcs(wcs) record['customweightname'] = 1.0 * (i + 1) record['id'] = i bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(i * 1000, i * 1000)) record.setBBox(bbox) mycatalog.append(record) # create the coaddpsf mypsf = measAlg.CoaddPsf(mycatalog, self.wcsref, 'customweightname') # check to be sure that we got the right number of components, in the right order self.assertEqual(mypsf.getComponentCount(), 9) for i in range(1, 10, 1): wcs = mypsf.getWcs(i - 1) psf = mypsf.getPsf(i - 1) bbox = mypsf.getBBox(i - 1) weight = mypsf.getWeight(i - 1) id = mypsf.getId(i - 1) self.assertEqual(i, id) self.assertEqual(weight, 1.0 * (i + 1)) self.assertEqual(bbox.getBeginX(), 0) self.assertEqual(bbox.getBeginY(), 0) self.assertEqual(bbox.getEndX(), 1000 * i) self.assertEqual(bbox.getEndY(), 1000 * i) self.assertAlmostEqual(wcs.getPixelOrigin().getX(), (1000.0 * i)) self.assertAlmostEqual(wcs.getPixelOrigin().getY(), (1000.0 * i)) m0, xbar, ybar, mxx, myy, x0, y0 = getPsfMoments( psf, lsst.geom.Point2D(0, 0)) self.assertAlmostEqual(i * i, mxx, delta=0.01) self.assertAlmostEqual(i * i, myy, delta=0.01)
def getCoaddPsf(self, exposure): import lsst.afw.table as afwTable import lsst.afw.image as afwImage import lsst.afw.math as afwMath import lsst.afw.geom as afwGeom import lsst.meas.algorithms as measAlg schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("weight", type="D", doc="Coadd weight") mycatalog = afwTable.ExposureCatalog(schema) wcsref = exposure.getWcs() extentX = int(exposure.getWidth() * 0.05) extentY = int(exposure.getHeight() * 0.05) ind = 0 for x in np.linspace(extentX, exposure.getWidth() - extentX, 10): for y in np.linspace(extentY, exposure.getHeight() - extentY, 10): x = int(x) y = int(y) image = self.getImage(x, y) psf = afwImage.ImageD(image.shape[0], image.shape[1]) psf.getArray()[:, :] = image psfK = afwMath.FixedKernel(psf) psf = measAlg.KernelPsf(psfK) record = mycatalog.getTable().makeRecord() record.setPsf(psf) record.setWcs(wcsref) bbox = afwGeom.Box2I( afwGeom.Point2I( int(np.floor(x - extentX)) - 5, int(np.floor(y - extentY)) - 5), afwGeom.Point2I( int(np.floor(x + extentX)) + 5, int(np.floor(y + extentY)) + 5)) record.setBBox(bbox) record['weight'] = 1.0 record['id'] = ind ind += 1 mycatalog.append(record) # create the coaddpsf psf = measAlg.CoaddPsf(mycatalog, wcsref, 'weight') return psf
def testDefaultSize(self): """Test of both default size and specified size""" print "DefaultSizeTest" sigma0 = 5 # set the peak of the outer guassian to 0 so this is really a single gaussian. psf = measAlg.DoubleGaussianPsf(60, 60, 1.5 * sigma0, 1, 0.0) if False and display: im = psf.computeImage(afwGeom.PointD(xwid / 2, ywid / 2)) ds9.mtv(im, title="N(%g) psf" % sigma0, frame=0) # this is the coadd Wcs we want cd11 = 5.55555555e-05 cd12 = 0.0 cd21 = 0.0 cd22 = 5.55555555e-05 crval1 = 0.0 crval2 = 0.0 crpix = afwGeom.PointD(1000, 1000) crval = afwCoord.Coord(afwGeom.Point2D(crval1, crval2)) wcsref = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) # Now make the catalog schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("weight", type="D", doc="Coadd weight") mycatalog = afwTable.ExposureCatalog(schema) record = mycatalog.getTable().makeRecord() psf = measAlg.DoubleGaussianPsf(100, 100, 10.0, 1.00, 1.0) record.setPsf(psf) wcs = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) record.setWcs(wcs) record['weight'] = 1.0 record['id'] = 1 bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(2000, 2000)) record.setBBox(bbox) mycatalog.append(record) mypsf = measAlg.CoaddPsf(mycatalog, wcsref) #, 'weight') m1coadd, m2coadd = getCoaddSecondMoments(mypsf, afwGeom.Point2D(0, 0)) m1, m2 = getPsfSecondMoments(mypsf, afwGeom.Point2D(1000, 1000)) self.assertTrue(testRelDiff(m1, m1coadd, .01)) self.assertTrue(testRelDiff(m2, m2coadd, .01))
def testFractionalPixel(self): """Check that we can create a CoaddPsf with 10 elements""" print "FractionalPixelTest" # this is the coadd Wcs we want cd11 = 5.55555555e-05 cd12 = 0.0 cd21 = 0.0 cd22 = 5.55555555e-05 crval1 = 0.0 crval2 = 0.0 crpix = afwGeom.PointD(1000, 1000) crval = afwCoord.Coord(afwGeom.Point2D(crval1, crval2)) wcsref = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) cd21 = 5.55555555e-05 cd12 = 5.55555555e-05 cd11 = 0.0 cd22 = 0.0 wcs = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("weight", type="D", doc="Coadd weight") mycatalog = afwTable.ExposureCatalog(schema) # make a single record with an oblong Psf record = mycatalog.getTable().makeRecord() psf = makeBiaxialGaussianPsf(100, 100, 6.0, 6.0, 0.0) record.setPsf(psf) record.setWcs(wcs) record['weight'] = 1.0 record['id'] = 1 bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(2000, 2000)) record.setBBox(bbox) mycatalog.append(record) mypsf = measAlg.CoaddPsf(mycatalog, wcsref) img = psf.computeImage(afwGeom.PointD(0.25, 0.75)) img = psf.computeImage(afwGeom.PointD(0.25, 0.75)) img = psf.computeImage(afwGeom.PointD(1000, 1000)) m0, xbar, ybar, mxx, myy, x0, y0 = getPsfMoments( psf, afwGeom.Point2D(0.25, 0.75)) cm0, cxbar, cybar, cmxx, cmyy, cx0, cy0 = getPsfMoments( mypsf, afwGeom.Point2D(0.25, 0.75)) self.assertTrue(testRelDiff(x0 + xbar, cx0 + cxbar, .01)) self.assertTrue(testRelDiff(y0 + ybar, cy0 + cybar, .01))
def testGoodPix(self): """Demonstrate that we can goodPix information in the CoaddPsf""" cd11, cd12, cd21, cd22 = 5.55555555e-05, 0.0, 0.0, 5.55555555e-05 crval1, crval2 = 0.0, 0.0 bboxSize = afwGeom.Extent2I(2000, 2000) crpix = afwGeom.PointD(bboxSize / 2.0) crval = afwCoord.Coord(afwGeom.Point2D(crval1, crval2)) wcsref = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("weight", type="D", doc="Coadd weight") schema.addField("goodpix", type="I", doc="Number of good pixels") mycatalog = afwTable.ExposureCatalog(schema) # Create several records, each with its own peculiar center and numGoodPixels. # Each PSF has the same shape and size, and the position offsets are small # relative to the FWHM, in order to make it easy to predict the resulting # weighted mean position. xwsum = 0 ywsum = 0 wsum = 0 for i, (xOff, yOff, numGoodPix) in enumerate(( (30.0, -20.0, 25), (32.0, -21.0, 10), (28.0, -19.0, 30), )): xwsum -= xOff * numGoodPix ywsum -= yOff * numGoodPix wsum += numGoodPix record = mycatalog.getTable().makeRecord() record.setPsf(measAlg.DoubleGaussianPsf(25, 25, 10, 1.00, 0.0)) offPix = crpix + afwGeom.Extent2D(xOff, yOff) wcs = afwImage.makeWcs(crval, offPix, cd11, cd12, cd21, cd22) record.setWcs(wcs) record['weight'] = 1.0 record['id'] = i record['goodpix'] = numGoodPix record.setBBox(afwGeom.Box2I(afwGeom.Point2I(0, 0), bboxSize)) mycatalog.append(record) mypsf = measAlg.CoaddPsf(mycatalog, wcsref, 'weight') predPos = afwGeom.Point2D(xwsum / wsum, ywsum / wsum) self.assertPairsNearlyEqual(predPos, mypsf.getAveragePosition())
def testValidPolygonPsf(self): """Demonstrate that we can use the validPolygon on Exposures in the CoaddPsf""" cd11, cd12, cd21, cd22 = 5.55555555e-05, 0.0, 0.0, 5.55555555e-05 crval1, crval2 = 0.0, 0.0 crpix = afwGeom.PointD(1000, 1000) crval = afwCoord.Coord(afwGeom.Point2D(crval1, crval2)) wcsref = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("weight", type="D", doc="Coadd weight") mycatalog = afwTable.ExposureCatalog(schema) # Create 9 separate records, each with its own peculiar Psf, Wcs, # weight, bounding box, and valid region. for i in range(1, 10): record = mycatalog.getTable().makeRecord() record.setPsf(measAlg.DoubleGaussianPsf(100, 100, i, 1.00, 0.0)) crpix = afwGeom.PointD(1000 - 10.0 * i, 1000.0 - 10.0 * i) wcs = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) record.setWcs(wcs) record['weight'] = 1.0 * (i + 1) record['id'] = i record.setBBox( afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1000, 1000))) validPolygon = Polygon( afwGeom.Box2D(afwGeom.Point2D(0, 0), afwGeom.Extent2D(i * 100, i * 100))) record.setValidPolygon(validPolygon) mycatalog.append(record) # Create the CoaddPsf and check at three different points to ensure that the validPolygon is working mypsf = measAlg.CoaddPsf(mycatalog, wcsref, 'weight') for position in [ afwGeom.Point2D(50, 50), afwGeom.Point2D(500, 500), afwGeom.Point2D(850, 850) ]: m1coadd, m2coadd = getCoaddSecondMoments(mypsf, position, True) m1, m2 = getPsfSecondMoments(mypsf, position) self.assertAlmostEqual(m1, m1coadd, delta=0.01) self.assertAlmostEqual(m2, m2coadd, delta=0.01)
def testWeight(self): """Check that we can measure a single Gaussian's attributes""" print "WeightTest" sigma0 = 5 # set the peak of the outer guassian to 0 so this is really a single gaussian. psf = measAlg.DoubleGaussianPsf(60, 60, 1.5 * sigma0, 1, 0.0) if False and display: im = psf.computeImage(afwGeom.PointD(xwid / 2, ywid / 2)) ds9.mtv(im, title="N(%g) psf" % sigma0, frame=0) # this is the coadd Wcs we want cd11 = 5.55555555e-05 cd12 = 0.0 cd21 = 0.0 cd22 = 5.55555555e-05 crval1 = 0.0 crval2 = 0.0 crpix = afwGeom.PointD(1000, 1000) crval = afwCoord.Coord(afwGeom.Point2D(crval1, crval2)) wcsref = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("weight", type="D", doc="Coadd weight") mycatalog = afwTable.ExposureCatalog(schema) sigma = [5, 6, 7, 8] # 5 pixels is the same as a sigma of 1 arcsec. # lay down a simple pattern of four ccds, set in a pattern of 1000 pixels around the center offsets = [(1999, 1999), (1999, 0), (0, 0), (0, 1999)] # Imagine a ccd in each of positions +-1000 pixels from the center for i in range(4): record = mycatalog.getTable().makeRecord() psf = measAlg.DoubleGaussianPsf(100, 100, sigma[i], 1.00, 0.0) record.setPsf(psf) crpix = afwGeom.PointD(offsets[i][0], offsets[i][1]) wcs = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) # print out the coorinates of this supposed 2000x2000 ccd in wcsref coordinates record.setWcs(wcs) record['weight'] = 1.0 * (i + 1) record['id'] = i bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(2000, 2000)) record.setBBox(bbox) mycatalog.append(record) mypsf = measAlg.CoaddPsf(mycatalog, wcsref) #, 'weight') m1, m2 = getPsfSecondMoments(mypsf, afwGeom.Point2D(1000, 1000)) m1coadd, m2coadd = getCoaddSecondMoments(mypsf, afwGeom.Point2D(1000, 1000)) self.assertTrue(testRelDiff(m1, m1coadd, .01)) m1, m2 = getPsfSecondMoments(mypsf, afwGeom.Point2D(1000, 1001)) m1coadd, m2coadd = getCoaddSecondMoments(mypsf, afwGeom.Point2D(1000, 1001)) self.assertTrue(testRelDiff(m1, m1coadd, .01)) m1, m2 = getCoaddSecondMoments(mypsf, afwGeom.Point2D(1001, 1000)) m1coadd, m2coadd = getCoaddSecondMoments(mypsf, afwGeom.Point2D(1001, 1000)) self.assertTrue(testRelDiff(m1, m1coadd, .01))
matchedCat = [] truthPositions = [] for i, cat in enumerate(catalogs): print("Matching catalog {}".format(i)) match, positions = matchCatalogs(cat) matchedCat.append(match) truthPositions.append(positions) truthPositions = np.array(truthPositions) stamps = [] replaced = [] variance = [] filteredCat = [] psfList = [] expCat = afwTable.ExposureCatalog(afwTable.ExposureTable.makeMinimalSchema()) place = 0 for im, cat, positions in zip(images, matchedCat, truthPositions): print("processing {}".format(place)) result = filterObjects(im, cat, positions, expCat) stamps += result[0] replaced += result[1] variance += result[2] filteredCat.append(result[3]) psfList += result[4] place += 1 finalCatalog = afwTable.SourceCatalog(filteredCat[0].schema) finalCatalog.reserve(len(stamps)) for n in range(len(filteredCat)):
def setUp(self): tract = 0 band = 'r' patch = 0 visits = [100, 101] # Good to test crossing 0. ra_center = 0.0 dec_center = -45.0 pixel_scale = 0.2 coadd_zp = 27.0 # Generate a mock skymap with one patch config = DiscreteSkyMap.ConfigClass() config.raList = [ra_center] config.decList = [dec_center] config.radiusList = [150 * pixel_scale / 3600.] config.patchInnerDimensions = (350, 350) config.patchBorder = 50 config.tractOverlap = 0.0 config.pixelScale = pixel_scale sky_map = DiscreteSkyMap(config) visit_summaries = [ makeMockVisitSummary(visit, ra_center=ra_center, dec_center=dec_center) for visit in visits ] visit_summary_refs = [ MockVisitSummaryReference(visit_summary, visit) for visit_summary, visit in zip(visit_summaries, visits) ] self.visit_summary_dict = { visit: ref.get() for ref, visit in zip(visit_summary_refs, visits) } # Generate an input map. Note that this does not need to be consistent # with the visit_summary projections, we're just tracking values. input_map = hsp.HealSparseMap.make_empty( nside_coverage=256, nside_sparse=32768, dtype=hsp.WIDE_MASK, wide_mask_maxbits=len(visits) * 2) patch_poly = afwGeom.Polygon( geom.Box2D(sky_map[tract][patch].getOuterBBox())) sph_pts = sky_map[tract].getWcs().pixelToSky( patch_poly.convexHull().getVertices()) patch_poly_radec = np.array([(sph.getRa().asDegrees(), sph.getDec().asDegrees()) for sph in sph_pts]) poly = hsp.Polygon(ra=patch_poly_radec[:-1, 0], dec=patch_poly_radec[:-1, 1], value=[0]) poly_pixels = poly.get_pixels(nside=input_map.nside_sparse) # The input map has full coverage for bits 0 and 1 input_map.set_bits_pix(poly_pixels, [0]) input_map.set_bits_pix(poly_pixels, [1]) input_map_ref = MockInputMapReference(input_map, patch=patch, tract=tract) self.input_map_dict = {patch: input_map_ref} coadd = afwImage.ExposureF(sky_map[tract][patch].getOuterBBox(), sky_map[tract].getWcs()) instFluxMag0 = 10.**(coadd_zp / 2.5) pc = afwImage.makePhotoCalibFromCalibZeroPoint(instFluxMag0) coadd.setPhotoCalib(pc) # Mock the coadd input ccd table schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("ccd", type="I") schema.addField("visit", type="I") schema.addField("weight", type="F") ccds = afwTable.ExposureCatalog(schema) ccds.resize(2) ccds['id'] = np.arange(2) ccds['visit'][0] = visits[0] ccds['visit'][1] = visits[1] ccds['ccd'][0] = 0 ccds['ccd'][1] = 1 ccds['weight'] = 10.0 for ccd_row in ccds: summary = self.visit_summary_dict[ccd_row['visit']].find( ccd_row['ccd']) ccd_row.setWcs(summary.getWcs()) ccd_row.setPsf(summary.getPsf()) ccd_row.setBBox(summary.getBBox()) ccd_row.setPhotoCalib(summary.getPhotoCalib()) inputs = afwImage.CoaddInputs() inputs.ccds = ccds coadd.getInfo().setCoaddInputs(inputs) coadd_ref = MockCoaddReference(coadd, patch=patch, tract=tract) self.coadd_dict = {patch: coadd_ref} self.tract = tract self.band = band self.sky_map = sky_map self.input_map = input_map
def testCoaddApCorrMap(self): """Check that we can create and use a coadd ApCorrMap.""" coaddBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(100, 100)) scale = 5.0e-5*afwGeom.degrees cdMatrix = afwGeom.makeCdMatrix(scale=scale) crval = afwGeom.SpherePoint(0.0, 0.0, afwGeom.degrees) center = afwGeom.Point2D(afwGeom.Extent2D(coaddBox.getDimensions())*0.5) coaddWcs = afwGeom.makeSkyWcs(crpix=afwGeom.Point2D(0, 0), crval=crval, cdMatrix=cdMatrix) schema = afwTable.ExposureTable.makeMinimalSchema() weightKey = schema.addField("customweightname", type="D", doc="Coadd weight") catalog = afwTable.ExposureCatalog(schema) # Non-overlapping num = 5 inputBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(10, 10)) validBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(7, 7)) pointList = [] pointListValid = [] for i in range(num): value = np.array([[1]], dtype=float) # Constant with value = i+1 apCorrMap = afwImage.ApCorrMap() bf = afwMath.ChebyshevBoundedField(inputBox, value*(i + 1)) apCorrMap.set("only", bf) point = afwGeom.Point2D(0, 0) - afwGeom.Extent2D(coaddBox.getDimensions())*(i+0.5)/num wcs = afwGeom.makeSkyWcs(crpix=point, crval=crval, cdMatrix=cdMatrix) center = afwGeom.Box2D(inputBox).getCenter() pointList.append(coaddWcs.skyToPixel(wcs.pixelToSky(center))) # This point will only be valid for the second overlapping record pointValid = center + afwGeom.Extent2D(4, 4) pointListValid.append(coaddWcs.skyToPixel(wcs.pixelToSky(pointValid))) # A record with the valid polygon defining a limited region record = catalog.getTable().makeRecord() record.setWcs(wcs) record.setBBox(inputBox) record.setApCorrMap(apCorrMap) record.set(weightKey, i + 1) record['id'] = i record.setValidPolygon(afwGeom.Polygon(afwGeom.Box2D(validBox))) catalog.append(record) # An overlapping record with the whole region as valid record = catalog.getTable().makeRecord() record.setWcs(wcs) record.setBBox(inputBox) apCorrMap = afwImage.ApCorrMap() bf = afwMath.ChebyshevBoundedField(inputBox, value*(i + 2)) apCorrMap.set("only", bf) record.setApCorrMap(apCorrMap) record.set(weightKey, i + 2) record['id'] = i + num record.setValidPolygon(afwGeom.Polygon(afwGeom.Box2D(inputBox))) catalog.append(record) apCorrMap = measAlg.makeCoaddApCorrMap(catalog, coaddBox, coaddWcs, "customweightname") # This will test a point where both records contribute self.assertApCorrMap(apCorrMap, pointList) # Only the second record will be valid for this point self.assertApCorrMapValid(apCorrMap, pointListValid) filename = os.path.join(os.path.dirname(os.path.realpath(__file__)), "coaddApCorrMap.fits") exposure = afwImage.ExposureF(1, 1) exposure.getInfo().setApCorrMap(apCorrMap) exposure.writeFits(filename) exposure = afwImage.ExposureF(filename) self.assertApCorrMap(exposure.getInfo().getApCorrMap(), pointList) self.assertApCorrMapValid(exposure.getInfo().getApCorrMap(), pointListValid) os.unlink(filename)
def _outputZeropoints(self, camera, zptCat, visitCat, offsets, bands, physicalFilterMap, tract=None): """Output the zeropoints in fgcm_photoCalib format. Parameters ---------- camera : `lsst.afw.cameraGeom.Camera` Camera from the butler. zptCat : `lsst.afw.table.BaseCatalog` FGCM zeropoint catalog from `FgcmFitCycleTask`. visitCat : `lsst.afw.table.BaseCatalog` FGCM visitCat from `FgcmBuildStarsTask`. offsets : `numpy.array` Float array of absolute calibration offsets, one for each filter. bands : `list` [`str`] List of band names from FGCM output. physicalFilterMap : `dict` Dictionary of mappings from physical filter to FGCM band. tract: `int`, optional Tract number to output. Default is None (global calibration) Returns ------- photoCalibCatalogs : `generator` [(`int`, `lsst.afw.table.ExposureCatalog`)] Generator that returns (visit, exposureCatalog) tuples. """ # Select visit/ccds where we have a calibration # This includes ccds where we were able to interpolate from neighboring # ccds. cannot_compute = fgcm.fgcmUtilities.zpFlagDict[ 'CANNOT_COMPUTE_ZEROPOINT'] selected = (((zptCat['fgcmFlag'] & cannot_compute) == 0) & (zptCat['fgcmZptVar'] > 0.0) & (zptCat['fgcmZpt'] > FGCM_ILLEGAL_VALUE)) # Log warnings for any visit which has no valid zeropoints badVisits = np.unique(zptCat['visit'][~selected]) goodVisits = np.unique(zptCat['visit'][selected]) allBadVisits = badVisits[~np.isin(badVisits, goodVisits)] for allBadVisit in allBadVisits: self.log.warning(f'No suitable photoCalib for visit {allBadVisit}') # Get a mapping from filtername to the offsets offsetMapping = {} for f in physicalFilterMap: # Not every filter in the map will necesarily have a band. if physicalFilterMap[f] in bands: offsetMapping[f] = offsets[bands.index(physicalFilterMap[f])] # Get a mapping from "ccd" to the ccd index used for the scaling ccdMapping = {} for ccdIndex, detector in enumerate(camera): ccdMapping[detector.getId()] = ccdIndex # And a mapping to get the flat-field scaling values scalingMapping = {} for rec in visitCat: scalingMapping[rec['visit']] = rec['scaling'] if self.config.doComposeWcsJacobian: approxPixelAreaFields = computeApproxPixelAreaFields(camera) # The zptCat is sorted by visit, which is useful lastVisit = -1 zptVisitCatalog = None metadata = dafBase.PropertyList() metadata.add("COMMENT", "Catalog id is detector id, sorted.") metadata.add("COMMENT", "Only detectors with data have entries.") for rec in zptCat[selected]: # Retrieve overall scaling scaling = scalingMapping[rec['visit']][ccdMapping[rec['detector']]] # The postCalibrationOffset describe any zeropoint offsets # to apply after the fgcm calibration. The first part comes # from the reference catalog match (used in testing). The # second part comes from the mean chromatic correction # (if configured). postCalibrationOffset = offsetMapping[rec['filtername']] if self.config.doApplyMeanChromaticCorrection: postCalibrationOffset += rec['fgcmDeltaChrom'] fgcmSuperStarField = self._getChebyshevBoundedField( rec['fgcmfZptSstarCheb'], rec['fgcmfZptChebXyMax']) # Convert from FGCM AB to nJy fgcmZptField = self._getChebyshevBoundedField( (rec['fgcmfZptCheb'] * units.AB).to_value(units.nJy), rec['fgcmfZptChebXyMax'], offset=postCalibrationOffset, scaling=scaling) if self.config.doComposeWcsJacobian: fgcmField = afwMath.ProductBoundedField([ approxPixelAreaFields[rec['detector']], fgcmSuperStarField, fgcmZptField ]) else: # The photoCalib is just the product of the fgcmSuperStarField and the # fgcmZptField fgcmField = afwMath.ProductBoundedField( [fgcmSuperStarField, fgcmZptField]) # The "mean" calibration will be set to the center of the ccd for reference calibCenter = fgcmField.evaluate(fgcmField.getBBox().getCenter()) calibErr = (np.log(10.0) / 2.5) * calibCenter * np.sqrt( rec['fgcmZptVar']) photoCalib = afwImage.PhotoCalib(calibrationMean=calibCenter, calibrationErr=calibErr, calibration=fgcmField, isConstant=False) # Return full per-visit exposure catalogs if rec['visit'] != lastVisit: # This is a new visit. If the last visit was not -1, yield # the ExposureCatalog if lastVisit > -1: # ensure that the detectors are in sorted order, for fast lookups zptVisitCatalog.sort() yield (int(lastVisit), zptVisitCatalog) else: # We need to create a new schema zptExpCatSchema = afwTable.ExposureTable.makeMinimalSchema( ) zptExpCatSchema.addField('visit', type='L', doc='Visit number') # And start a new one zptVisitCatalog = afwTable.ExposureCatalog(zptExpCatSchema) zptVisitCatalog.setMetadata(metadata) lastVisit = int(rec['visit']) catRecord = zptVisitCatalog.addNew() catRecord['id'] = int(rec['detector']) catRecord['visit'] = rec['visit'] catRecord.setPhotoCalib(photoCalib) # Final output of last exposure catalog # ensure that the detectors are in sorted order, for fast lookups zptVisitCatalog.sort() yield (int(lastVisit), zptVisitCatalog)
def makeMockVisitSummary(visit, ra_center=0.0, dec_center=-45.0, physical_filter='TEST-I', band='i', mjd=59234.7083333334, psf_sigma=3.0, zenith_distance=45.0, zero_point=30.0, sky_background=100.0, sky_noise=10.0, mean_var=100.0, exposure_time=100.0, detector_size=200, pixel_scale=0.2): """Make a mock visit summary catalog. This will contain two square detectors with the same metadata, with a small (20 pixel) gap between the detectors. There is no rotation, as each detector is simply offset in RA from the specified boresight. Parameters ---------- visit : `int` Visit number. ra_center : `float` Right ascension of the center of the "camera" boresight (degrees). dec_center : `float` Declination of the center of the "camera" boresight (degrees). physical_filter : `str` Arbitrary name for the physical filter. band : `str` Name of the associated band. mjd : `float` Modified Julian Date. psf_sigma : `float` Sigma width of Gaussian psf. zenith_distance : `float` Distance from zenith of the visit (degrees). zero_point : `float` Constant zero point for the visit (magnitudes). sky_background : `float` Background level for the visit (counts). sky_noise : `float` Noise level for the background of the visit (counts). mean_var : `float` Mean of the variance plane of the visit (counts). exposure_time : `float` Exposure time of the visit (seconds). detector_size : `int` Size of each square detector in the visit (pixels). pixel_scale : `float` Size of the pixel in arcseconds per pixel. Returns ------- visit_summary : `lsst.afw.table.ExposureCatalog` """ # We are making a 2 detector "camera" n_detector = 2 schema = ConsolidateVisitSummaryTask()._makeVisitSummarySchema() visit_summary = afwTable.ExposureCatalog(schema) visit_summary.resize(n_detector) bbox = geom.Box2I(x=geom.IntervalI(min=0, max=detector_size - 1), y=geom.IntervalI(min=0, max=detector_size - 1)) for detector_id in range(n_detector): row = visit_summary[detector_id] row['id'] = detector_id row.setBBox(bbox) row['visit'] = visit row['physical_filter'] = physical_filter row['band'] = band row['zenithDistance'] = zenith_distance row['zeroPoint'] = zero_point row['skyBg'] = sky_background row['skyNoise'] = sky_noise row['meanVar'] = mean_var # Generate a photocalib instFluxMag0 = 10.**(zero_point / 2.5) row.setPhotoCalib( afwImage.makePhotoCalibFromCalibZeroPoint(instFluxMag0)) # Generate a WCS and set values accordingly crpix = geom.Point2D(detector_size / 2., detector_size / 2.) # Create a 20 pixel gap between the two detectors (each offset 10 pixels). if detector_id == 0: delta_ra = -1.0 * ((detector_size + 10) * pixel_scale / 3600.) / np.cos(np.deg2rad(dec_center)) delta_dec = 0.0 elif detector_id == 1: delta_ra = ((detector_size + 10) * pixel_scale / 3600.) / np.cos( np.deg2rad(dec_center)) delta_dec = 0.0 crval = geom.SpherePoint(ra_center + delta_ra, dec_center + delta_dec, geom.degrees) cd_matrix = afwGeom.makeCdMatrix(scale=pixel_scale * geom.arcseconds, orientation=0.0 * geom.degrees) wcs = afwGeom.makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cd_matrix) row.setWcs(wcs) sph_pts = wcs.pixelToSky(geom.Box2D(bbox).getCorners()) row['raCorners'] = np.array( [float(sph.getRa().asDegrees()) for sph in sph_pts]) row['decCorners'] = np.array( [float(sph.getDec().asDegrees()) for sph in sph_pts]) sph_pt = wcs.pixelToSky(bbox.getCenter()) row['ra'] = sph_pt.getRa().asDegrees() row['decl'] = sph_pt.getDec().asDegrees() # Generate a visitInfo. # This does not need to be consistent with the zenith angle in the table, # it just needs to be valid and have sufficient information to compute # exposure time and parallactic angle. date = DateTime(date=mjd, system=DateTime.DateSystem.MJD) visit_info = afwImage.VisitInfo( exposureId=visit, exposureTime=exposure_time, date=date, darkTime=0.0, boresightRaDec=geom.SpherePoint(ra_center, dec_center, geom.degrees), era=45.1 * geom.degrees, observatory=Observatory(11.1 * geom.degrees, 0.0 * geom.degrees, 0.333), boresightRotAngle=0.0 * geom.degrees, rotType=afwImage.RotType.SKY) row.setVisitInfo(visit_info) # Generate a PSF and set values accordingly psf = GaussianPsf(15, 15, psf_sigma) row.setPsf(psf) psfAvgPos = psf.getAveragePosition() shape = psf.computeShape(psfAvgPos) row['psfSigma'] = psf.getSigma() row['psfIxx'] = shape.getIxx() row['psfIyy'] = shape.getIyy() row['psfIxy'] = shape.getIxy() row['psfArea'] = shape.getArea() return visit_summary
def _combineExposureMetadata(self, visit, dataRefs, isGen3=True): """Make a combined exposure catalog from a list of dataRefs. Parameters ---------- visit : `int` Visit identification number dataRefs : `list` List of calexp dataRefs in visit. May be list of `lsst.daf.persistence.ButlerDataRef` (Gen2) or `lsst.daf.butler.DeferredDatasetHandle` (Gen3). isGen3 : `bool`, optional Specifies if this is a Gen3 list of datarefs. Returns ------- visitSummary : `lsst.afw.table.ExposureCatalog` Exposure catalog with per-detector summary information. """ schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField('visit', type='I', doc='Visit number') schema.addField('physical_filter', type='String', size=32, doc='Physical filter') schema.addField('band', type='String', size=32, doc='Name of band') schema.addField('psfSigma', type='F', doc='PSF model second-moments determinant radius (center of chip) (pixel)') schema.addField('psfArea', type='F', doc='PSF model effective area (center of chip) (pixel**2)') schema.addField('psfIxx', type='F', doc='PSF model Ixx (center of chip) (pixel**2)') schema.addField('psfIyy', type='F', doc='PSF model Iyy (center of chip) (pixel**2)') schema.addField('psfIxy', type='F', doc='PSF model Ixy (center of chip) (pixel**2)') schema.addField('raCorners', type='ArrayD', size=4, doc='Right Ascension of bounding box corners (degrees)') schema.addField('decCorners', type='ArrayD', size=4, doc='Declination of bounding box corners (degrees)') cat = afwTable.ExposureCatalog(schema) cat.resize(len(dataRefs)) cat['visit'] = visit for i, dataRef in enumerate(dataRefs): if isGen3: visitInfo = dataRef.get(component='visitInfo') filterLabel = dataRef.get(component='filterLabel') psf = dataRef.get(component='psf') wcs = dataRef.get(component='wcs') photoCalib = dataRef.get(component='photoCalib') detector = dataRef.get(component='detector') bbox = dataRef.get(component='bbox') validPolygon = dataRef.get(component='validPolygon') else: # Note that we need to read the calexp because there is # no magic access to the psf except through the exposure. gen2_read_bbox = lsst.geom.BoxI(lsst.geom.PointI(0, 0), lsst.geom.PointI(1, 1)) exp = dataRef.get(datasetType='calexp_sub', bbox=gen2_read_bbox) visitInfo = exp.getInfo().getVisitInfo() filterLabel = dataRef.get("calexp_filterLabel") psf = exp.getPsf() wcs = exp.getWcs() photoCalib = exp.getPhotoCalib() detector = exp.getDetector() bbox = dataRef.get(datasetType='calexp_bbox') validPolygon = exp.getInfo().getValidPolygon() rec = cat[i] rec.setBBox(bbox) rec.setVisitInfo(visitInfo) rec.setWcs(wcs) rec.setPhotoCalib(photoCalib) rec.setValidPolygon(validPolygon) rec['physical_filter'] = filterLabel.physicalLabel if filterLabel.hasPhysicalLabel() else "" rec['band'] = filterLabel.bandLabel if filterLabel.hasBandLabel() else "" rec.setId(detector.getId()) shape = psf.computeShape(bbox.getCenter()) rec['psfSigma'] = shape.getDeterminantRadius() rec['psfIxx'] = shape.getIxx() rec['psfIyy'] = shape.getIyy() rec['psfIxy'] = shape.getIxy() im = psf.computeKernelImage(bbox.getCenter()) # The calculation of effective psf area is taken from # meas_base/src/PsfFlux.cc#L112. See # https://github.com/lsst/meas_base/blob/ # 750bffe6620e565bda731add1509507f5c40c8bb/src/PsfFlux.cc#L112 rec['psfArea'] = np.sum(im.array)/np.sum(im.array**2.) sph_pts = wcs.pixelToSky(lsst.geom.Box2D(bbox).getCorners()) rec['raCorners'][:] = [sph.getRa().asDegrees() for sph in sph_pts] rec['decCorners'][:] = [sph.getDec().asDegrees() for sph in sph_pts] metadata = dafBase.PropertyList() metadata.add("COMMENT", "Catalog id is detector id, sorted.") # We are looping over existing datarefs, so the following is true metadata.add("COMMENT", "Only detectors with data have entries.") cat.setMetadata(metadata) cat.sort() return cat
def testSimpleGaussian(self): """Check that we can measure a single Gaussian's attributes""" print "SimpleGaussianTest" sigma0 = 5 # set the peak of the outer guassian to 0 so this is really a single gaussian. psf = measAlg.DoubleGaussianPsf(60, 60, 1.5 * sigma0, 1, 0.0) # this is the coadd Wcs we want cd11 = 5.55555555e-05 cd12 = 0.0 cd21 = 0.0 cd22 = 5.55555555e-05 crval1 = 0.0 crval2 = 0.0 crpix = afwGeom.PointD(1000, 1000) crval = afwCoord.Coord(afwGeom.Point2D(crval1, crval2)) wcsref = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("weight", type="D", doc="Coadd weight") mycatalog = afwTable.ExposureCatalog(schema) sigma = [5, 6, 7, 8] # 5 pixels is the same as a sigma of 1 arcsec. # lay down a simple pattern of four ccds, set in a pattern of 1000 pixels around the center offsets = [(1999, 1999), (1999, 0), (0, 0), (0, 1999)] # Imagine a ccd in each of positions +-1000 pixels from the center for i in range(4): record = mycatalog.getTable().makeRecord() psf = measAlg.DoubleGaussianPsf(100, 100, sigma[i], 1.00, 1.0) record.setPsf(psf) crpix = afwGeom.PointD(offsets[i][0], offsets[i][1]) wcs = afwImage.makeWcs(crval, crpix, cd11, cd12, cd21, cd22) # print out the coorinates of this supposed 2000x2000 ccd in wcsref coordinates beginCoord = wcs.pixelToSky(0, 0) endCoord = wcs.pixelToSky(2000, 2000) wcsref.skyToPixel(beginCoord) wcsref.skyToPixel(endCoord) record.setWcs(wcs) record['weight'] = 1.0 record['id'] = i bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(2000, 2000)) record.setBBox(bbox) mycatalog.append(record) #img = psf.computeImage(afwGeom.Point2D(1000,1000), afwGeom.Extent2I(100,100), False, False) #img.writeFits("img%d.fits"%i) mypsf = measAlg.CoaddPsf(mycatalog, wcsref) #, 'weight') m1coadd, m2coadd = getCoaddSecondMoments(mypsf, afwGeom.Point2D(1000, 1000)) m1, m2 = getPsfSecondMoments(mypsf, afwGeom.Point2D(1000, 1000)) self.assertAlmostEqual(m1, m1coadd, delta=.01) m1, m2 = getPsfSecondMoments(mypsf, afwGeom.Point2D(1000, 1001)) m1coadd, m2coadd = getCoaddSecondMoments(mypsf, afwGeom.Point2D(1000, 1001)) self.assertAlmostEqual(m1, m1coadd, delta=0.01)
def run(self, exposure, sensorRef, templateIdList=None): """Retrieve and mosaic a template coadd that overlaps the exposure where the template spans multiple tracts. The resulting template image will be an average of all the input templates from the separate tracts. The PSF on the template is created by combining the CoaddPsf on each template image into a meta-CoaddPsf. Parameters ---------- exposure: `lsst.afw.image.Exposure` an exposure for which to generate an overlapping template sensorRef : TYPE a Butler data reference that can be used to obtain coadd data templateIdList : TYPE, optional list of data ids (unused) Returns ------- result : `struct` return a pipeBase.Struct: - ``exposure`` : a template coadd exposure assembled out of patches - ``sources`` : None for this subtask """ # Table for CoaddPSF tractsSchema = afwTable.ExposureTable.makeMinimalSchema() tractKey = tractsSchema.addField('tract', type=np.int32, doc='Which tract') patchKey = tractsSchema.addField('patch', type=np.int32, doc='Which patch') weightKey = tractsSchema.addField( 'weight', type=float, doc='Weight for each tract, should be 1') tractsCatalog = afwTable.ExposureCatalog(tractsSchema) skyMap = sensorRef.get(datasetType=self.config.coaddName + "Coadd_skyMap") expWcs = exposure.getWcs() expBoxD = geom.Box2D(exposure.getBBox()) expBoxD.grow(self.config.templateBorderSize) ctrSkyPos = expWcs.pixelToSky(expBoxD.getCenter()) centralTractInfo = skyMap.findTract(ctrSkyPos) if not centralTractInfo: raise RuntimeError("No suitable tract found for central point") self.log.info("Central skyMap tract %s" % (centralTractInfo.getId(), )) skyCorners = [ expWcs.pixelToSky(pixPos) for pixPos in expBoxD.getCorners() ] tractPatchList = skyMap.findTractPatchList(skyCorners) if not tractPatchList: raise RuntimeError("No suitable tract found") self.log.info("All overlapping skyMap tracts %s" % ([a[0].getId() for a in tractPatchList])) # Move central tract to front of the list and use as the reference tracts = [tract[0].getId() for tract in tractPatchList] centralIndex = tracts.index(centralTractInfo.getId()) tracts.insert(0, tracts.pop(centralIndex)) tractPatchList.insert(0, tractPatchList.pop(centralIndex)) coaddPsf = None coaddFilter = None nPatchesFound = 0 maskedImageList = [] weightList = [] for itract, tract in enumerate(tracts): tractInfo = tractPatchList[itract][0] coaddWcs = tractInfo.getWcs() coaddBBox = geom.Box2D() for skyPos in skyCorners: coaddBBox.include(coaddWcs.skyToPixel(skyPos)) coaddBBox = geom.Box2I(coaddBBox) if itract == 0: # Define final wcs and bounding box from the reference tract finalWcs = coaddWcs finalBBox = coaddBBox patchList = tractPatchList[itract][1] for patchInfo in patchList: self.log.info('Adding patch %s from tract %s' % (patchInfo.getIndex(), tract)) # Local patch information patchSubBBox = geom.Box2I(patchInfo.getInnerBBox()) patchSubBBox.clip(coaddBBox) patchInt = int( f"{patchInfo.getIndex()[0]}{patchInfo.getIndex()[1]}") innerBBox = geom.Box2I(tractInfo._minimumBoundingBox(finalWcs)) if itract == 0: # clip to image and tract boundaries patchSubBBox.clip(finalBBox) patchSubBBox.clip(innerBBox) if patchSubBBox.getArea() == 0: self.log.debug("No ovlerap for patch %s" % patchInfo) continue patchArgDict = dict( datasetType="deepCoadd_sub", bbox=patchSubBBox, tract=tractInfo.getId(), patch="%s,%s" % (patchInfo.getIndex()[0], patchInfo.getIndex()[1]), filter=exposure.getFilter().getName()) coaddPatch = sensorRef.get(**patchArgDict) if coaddFilter is None: coaddFilter = coaddPatch.getFilter() # create full image from final bounding box exp = afwImage.ExposureF(finalBBox, finalWcs) exp.maskedImage.set( np.nan, afwImage.Mask.getPlaneBitMask("NO_DATA"), np.nan) exp.maskedImage.assign(coaddPatch.maskedImage, patchSubBBox) maskedImageList.append(exp.maskedImage) weightList.append(1) record = tractsCatalog.addNew() record.setPsf(coaddPatch.getPsf()) record.setWcs(coaddPatch.getWcs()) record.setPhotoCalib(coaddPatch.getPhotoCalib()) record.setBBox(patchSubBBox) record.set(tractKey, tract) record.set(patchKey, patchInt) record.set(weightKey, 1.) nPatchesFound += 1 else: # compute the exposure bounding box in a tract that is not the reference tract localBox = geom.Box2I() for skyPos in skyCorners: localBox.include( geom.Point2I( tractInfo.getWcs().skyToPixel(skyPos))) # clip to patch bounding box localBox.clip(patchSubBBox) # grow border to deal with warping at edges localBox.grow(self.config.templateBorderSize) # clip to tract inner bounding box localInnerBBox = geom.Box2I( tractInfo._minimumBoundingBox(tractInfo.getWcs())) localBox.clip(localInnerBBox) patchArgDict = dict( datasetType="deepCoadd_sub", bbox=localBox, tract=tractInfo.getId(), patch="%s,%s" % (patchInfo.getIndex()[0], patchInfo.getIndex()[1]), filter=exposure.getFilter().getName()) coaddPatch = sensorRef.get(**patchArgDict) # warp to reference tract wcs xyTransform = afwGeom.makeWcsPairTransform( coaddPatch.getWcs(), finalWcs) psfWarped = WarpedPsf(coaddPatch.getPsf(), xyTransform) warped = self.warper.warpExposure(finalWcs, coaddPatch, maxBBox=finalBBox) # check if warpped image is viable if warped.getBBox().getArea() == 0: self.log.info( "No ovlerap for warped patch %s. Skipping" % patchInfo) continue warped.setPsf(psfWarped) exp = afwImage.ExposureF(finalBBox, finalWcs) exp.maskedImage.set( np.nan, afwImage.Mask.getPlaneBitMask("NO_DATA"), np.nan) exp.maskedImage.assign(warped.maskedImage, warped.getBBox()) maskedImageList.append(exp.maskedImage) weightList.append(1) record = tractsCatalog.addNew() record.setPsf(psfWarped) record.setWcs(finalWcs) record.setPhotoCalib(coaddPatch.getPhotoCalib()) record.setBBox(warped.getBBox()) record.set(tractKey, tract) record.set(patchKey, patchInt) record.set(weightKey, 1.) nPatchesFound += 1 if nPatchesFound == 0: raise RuntimeError("No patches found!") # Combine images from individual patches together # Do not mask any values statsFlags = afwMath.stringToStatisticsProperty(self.config.statistic) maskMap = [] statsCtrl = afwMath.StatisticsControl() statsCtrl.setNanSafe(True) statsCtrl.setWeighted(True) statsCtrl.setCalcErrorFromInputVariance(True) coaddExposure = afwImage.ExposureF(finalBBox, finalWcs) coaddExposure.maskedImage.set(np.nan, afwImage.Mask.getPlaneBitMask("NO_DATA"), np.nan) xy0 = coaddExposure.getXY0() coaddExposure.maskedImage = afwMath.statisticsStack( maskedImageList, statsFlags, statsCtrl, weightList, 0, maskMap) coaddExposure.maskedImage.setXY0(xy0) coaddPsf = CoaddPsf(tractsCatalog, finalWcs, self.config.coaddPsf.makeControl()) if coaddPsf is None: raise RuntimeError("No coadd Psf found!") coaddExposure.setPsf(coaddPsf) coaddExposure.setFilter(coaddFilter) return pipeBase.Struct(exposure=coaddExposure, sources=None)
def run(self, coaddExposures, bbox, wcs, dataIds, **kwargs): """Warp coadds from multiple tracts to form a template for image diff. Where the tracts overlap, the resulting template image is averaged. The PSF on the template is created by combining the CoaddPsf on each template image into a meta-CoaddPsf. Parameters ---------- coaddExposures : `list` of `lsst.afw.image.Exposure` Coadds to be mosaicked bbox : `lsst.geom.Box2I` Template Bounding box of the detector geometry onto which to resample the coaddExposures wcs : `lsst.afw.geom.SkyWcs` Template WCS onto which to resample the coaddExposures dataIds : `list` of `lsst.daf.butler.DataCoordinate` Record of the tract and patch of each coaddExposure. **kwargs Any additional keyword parameters. Returns ------- result : `lsst.pipe.base.Struct` containing - ``outputExposure`` : a template coadd exposure assembled out of patches """ # Table for CoaddPSF tractsSchema = afwTable.ExposureTable.makeMinimalSchema() tractKey = tractsSchema.addField('tract', type=np.int32, doc='Which tract') patchKey = tractsSchema.addField('patch', type=np.int32, doc='Which patch') weightKey = tractsSchema.addField( 'weight', type=float, doc='Weight for each tract, should be 1') tractsCatalog = afwTable.ExposureCatalog(tractsSchema) finalWcs = wcs bbox.grow(self.config.templateBorderSize) finalBBox = bbox nPatchesFound = 0 maskedImageList = [] weightList = [] for coaddExposure, dataId in zip(coaddExposures, dataIds): # warp to detector WCS warped = self.warper.warpExposure(finalWcs, coaddExposure, maxBBox=finalBBox) # Check if warped image is viable if not np.any(np.isfinite(warped.image.array)): self.log.info("No overlap for warped %s. Skipping" % dataId) continue exp = afwImage.ExposureF(finalBBox, finalWcs) exp.maskedImage.set(np.nan, afwImage.Mask.getPlaneBitMask("NO_DATA"), np.nan) exp.maskedImage.assign(warped.maskedImage, warped.getBBox()) maskedImageList.append(exp.maskedImage) weightList.append(1) record = tractsCatalog.addNew() record.setPsf(coaddExposure.getPsf()) record.setWcs(coaddExposure.getWcs()) record.setPhotoCalib(coaddExposure.getPhotoCalib()) record.setBBox(coaddExposure.getBBox()) record.setValidPolygon( afwGeom.Polygon( geom.Box2D(coaddExposure.getBBox()).getCorners())) record.set(tractKey, dataId['tract']) record.set(patchKey, dataId['patch']) record.set(weightKey, 1.) nPatchesFound += 1 if nPatchesFound == 0: raise pipeBase.NoWorkFound("No patches found to overlap detector") # Combine images from individual patches together statsFlags = afwMath.stringToStatisticsProperty('MEAN') statsCtrl = afwMath.StatisticsControl() statsCtrl.setNanSafe(True) statsCtrl.setWeighted(True) statsCtrl.setCalcErrorFromInputVariance(True) templateExposure = afwImage.ExposureF(finalBBox, finalWcs) templateExposure.maskedImage.set( np.nan, afwImage.Mask.getPlaneBitMask("NO_DATA"), np.nan) xy0 = templateExposure.getXY0() # Do not mask any values templateExposure.maskedImage = afwMath.statisticsStack(maskedImageList, statsFlags, statsCtrl, weightList, clipped=0, maskMap=[]) templateExposure.maskedImage.setXY0(xy0) # CoaddPsf centroid not only must overlap image, but must overlap the part of # image with data. Use centroid of region with data boolmask = templateExposure.mask.array & templateExposure.mask.getPlaneBitMask( 'NO_DATA') == 0 maskx = afwImage.makeMaskFromArray(boolmask.astype(afwImage.MaskPixel)) centerCoord = afwGeom.SpanSet.fromMask(maskx, 1).computeCentroid() ctrl = self.config.coaddPsf.makeControl() coaddPsf = CoaddPsf(tractsCatalog, finalWcs, centerCoord, ctrl.warpingKernelName, ctrl.cacheSize) if coaddPsf is None: raise RuntimeError("CoaddPsf could not be constructed") templateExposure.setPsf(coaddPsf) templateExposure.setFilter(coaddExposure.getFilter()) templateExposure.setPhotoCalib(coaddExposure.getPhotoCalib()) return pipeBase.Struct(outputExposure=templateExposure)
def run(self, visit, band, isolated_star_cat_dict, isolated_star_source_dict, src_dict, calexp_dict): """ Run the FinalizeCharacterizationTask. Parameters ---------- visit : `int` Visit number. Used in the output catalogs. band : `str` Band name. Used to select reserved stars. isolated_star_cat_dict : `dict` Per-tract dict of isolated star catalog handles. isolated_star_source_dict : `dict` Per-tract dict of isolated star source catalog handles. src_dict : `dict` Per-detector dict of src catalog handles. calexp_dict : `dict` Per-detector dict of calibrated exposure handles. Returns ------- struct : `lsst.pipe.base.struct` Struct with outputs for persistence. """ # We do not need the isolated star table in this task. # However, it is used in tests to confirm consistency of indexes. _, isolated_source_table = self.concat_isolated_star_cats( band, isolated_star_cat_dict, isolated_star_source_dict ) exposure_cat_schema = afwTable.ExposureTable.makeMinimalSchema() exposure_cat_schema.addField('visit', type='L', doc='Visit number') metadata = dafBase.PropertyList() metadata.add("COMMENT", "Catalog id is detector id, sorted.") metadata.add("COMMENT", "Only detectors with data have entries.") psf_ap_corr_cat = afwTable.ExposureCatalog(exposure_cat_schema) psf_ap_corr_cat.setMetadata(metadata) measured_src_tables = [] for detector in src_dict: src = src_dict[detector].get() exposure = calexp_dict[detector].get() psf, ap_corr_map, measured_src = self.compute_psf_and_ap_corr_map( visit, detector, exposure, src, isolated_source_table ) # And now we package it together... record = psf_ap_corr_cat.addNew() record['id'] = int(detector) record['visit'] = visit if psf is not None: record.setPsf(psf) if ap_corr_map is not None: record.setApCorrMap(ap_corr_map) measured_src['visit'][:] = visit measured_src['detector'][:] = detector measured_src_tables.append(measured_src.asAstropy().as_array()) measured_src_table = np.concatenate(measured_src_tables) return pipeBase.Struct(psf_ap_corr_cat=psf_ap_corr_cat, output_table=measured_src_table)