def testGainAndReadnoise(self):
        import lsst.afw.image as afwImage
        from lsst.afw.cameraGeom.testUtils import DetectorWrapper
        from lsst.ip.isr import IsrTask

        isrTask = IsrTask()

        detector = DetectorWrapper().detector
        raw = afwImage.ExposureF(detector.getBBox())

        level = 10
        readNoise = 1
        raw.image.set(level)

        amp = detector[0]
        amp.setReadNoise(readNoise)

        for gain in [-1, 0, 0.1, 1]:
            amp.setGain(gain)
            isrTask.updateVariance(raw, amp)
            if gain <= 0:
                gain = 1

        self.assertEqual(raw.variance[0, 0, afwImage.LOCAL],
                         level / gain + readNoise**2)
Example #2
0
    def setUp(self):
        maskedImage = afwImage.MaskedImageF(inFilePathSmall)
        maskedImageMD = afwImage.readMetadata(inFilePathSmall)

        self.smallExposure = afwImage.ExposureF(inFilePathSmall)
        self.width = maskedImage.getWidth()
        self.height = maskedImage.getHeight()
        self.wcs = afwImage.makeWcs(maskedImageMD)
        self.psf = DummyPsf(2.0)
        self.detector = DetectorWrapper().detector

        self.exposureBlank = afwImage.ExposureF()
        self.exposureMiOnly = afwImage.makeExposure(maskedImage)
        self.exposureMiWcs = afwImage.makeExposure(maskedImage, self.wcs)
        self.exposureCrWcs = afwImage.ExposureF(
            100, 100, self.wcs)  # n.b. the (100, 100, ...) form
        self.exposureCrOnly = afwImage.ExposureF(afwGeom.ExtentI(
            100, 100))  # test with ExtentI(100, 100) too

        afwImage.Filter.reset()
        afwImage.FilterProperty.reset()

        filterPolicy = pexPolicy.Policy()
        filterPolicy.add("lambdaEff", 470.0)
        afwImage.Filter.define(afwImage.FilterProperty("g", filterPolicy))
Example #3
0
    def testConstructorErrors(self):
        """Test constructor errors
        """
        def duplicateAmpName(dw):
            """Set two amplifiers to the same name"""
            dw.ampInfo[1].setName(dw.ampInfo[0].getName())

        with self.assertRaises(lsst.pex.exceptions.Exception):
            DetectorWrapper(modFunc=duplicateAmpName)

        def addBadCameraSys(dw):
            """Add an invalid camera system"""
            dw.transMap[cameraGeom.CameraSys("foo", "wrong detector")] = \
                afwGeom.makeIdentityTransform()

        with self.assertRaises(lsst.pex.exceptions.Exception):
            DetectorWrapper(modFunc=addBadCameraSys)

        # These break in the pybind layer
        for crosstalk in (
            [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],  # 1D and not numpy
                np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0,
                          9.0]),  # 1D, wrong numpy type
                np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
                         dtype=np.float32),  # 1D
        ):
            self.assertRaises(TypeError, DetectorWrapper, crosstalk=crosstalk)
        # These break in the Detector ctor: wrong shape
        self.assertRaises(lsst.pex.exceptions.InvalidParameterError,
                          DetectorWrapper,
                          crosstalk=np.array([[1.0, 2.0], [3.0, 4.0]]))
        self.assertRaises(lsst.pex.exceptions.InvalidParameterError,
                          DetectorWrapper,
                          crosstalk=np.array([[1.0, 2.0], [3.0, 4.0],
                                              [5.0, 6.0]]))
Example #4
0
    def testGainAndReadnoise(self):

        isrTask = IsrTask()

        detector = DetectorWrapper().detector
        raw = afwImage.ExposureF(detector.getBBox())

        level = 10
        readNoise = 1.5
        raw.image.set(level)

        amp = detector[0]

        for gain in [-1, 0, 0.1, 1, np.NaN]:
            # Because amplifiers are immutable, we can't change the gain or
            # read noise in-place. Instead, we clone, and update the clone.
            testAmp = Amplifier.Builder()
            testAmp.assign(amp)
            testAmp.setReadNoise(readNoise)
            testAmp.setGain(gain)
            testAmp.finish()

            isrTask.updateVariance(raw, testAmp)
            if gain <= 0:  # behave the same way as amp.setGain
                gain = 1
            if math.isnan(gain):
                gain = 1
            self.assertEqual(raw.variance[0, 0, afwImage.LOCAL],
                             level / gain + readNoise**2)
Example #5
0
    def setUp(self):
        maskedImage = afwImage.MaskedImageF(inFilePathSmall)
        maskedImageMD = readMetadata(inFilePathSmall)

        self.smallExposure = afwImage.ExposureF(inFilePathSmall)
        self.width = maskedImage.getWidth()
        self.height = maskedImage.getHeight()
        self.wcs = afwGeom.makeSkyWcs(maskedImageMD, False)
        self.md = maskedImageMD
        self.psf = DummyPsf(2.0)
        self.detector = DetectorWrapper().detector
        self.extras = {"misc": DummyPsf(3.5)}

        self.exposureBlank = afwImage.ExposureF()
        self.exposureMiOnly = afwImage.makeExposure(maskedImage)
        self.exposureMiWcs = afwImage.makeExposure(maskedImage, self.wcs)
        # n.b. the (100, 100, ...) form
        self.exposureCrWcs = afwImage.ExposureF(100, 100, self.wcs)
        # test with ExtentI(100, 100) too
        self.exposureCrOnly = afwImage.ExposureF(lsst.geom.ExtentI(100, 100))

        afwImage.Filter.reset()
        afwImage.FilterProperty.reset()

        defineFilter("g", 470.0)
Example #6
0
    def testDetectorRebuild(self):
        """Test Detector.rebuild() method
        """
        #
        # Make a copy without any modifications
        #
        detector = DetectorWrapper().detector
        ndetector = detector.rebuild().finish()

        self.assertEqual(detector.getName(), ndetector.getName())
        self.assertEqual(detector.getBBox(), ndetector.getBBox())
        self.assertEqual(detector.getPhysicalType(),
                         ndetector.getPhysicalType())
        for amp, namp in zip(detector, ndetector):
            self.assertEqual(amp.getBBox(), namp.getBBox())
            self.assertEqual(amp.getRawXYOffset(), namp.getRawXYOffset())
        #
        # Now make a copy with a hacked-up set of amps
        #
        builder = detector.rebuild()
        for i, amp in enumerate(builder, 1):
            amp.setRawXYOffset(i * lsst.geom.ExtentI(1, 1))

        ndetector = builder.finish()

        self.assertEqual(detector.getName(), ndetector.getName())
        self.assertEqual(detector.getBBox(), ndetector.getBBox())
        for i, (amp, namp) in enumerate(zip(detector, ndetector), 1):
            self.assertEqual(amp.getBBox(), namp.getBBox())
            self.assertNotEqual(amp.getRawXYOffset(), namp.getRawXYOffset())
            self.assertEqual(namp.getRawXYOffset()[0], i)
    def testSetPolygonIntersect(self):
        # Create a detector
        detector = DetectorWrapper().detector
        numPolygonPoints = 50
        # Create an exposure with bounding box defined by detector
        exposure = afwImage.ExposureF(detector.getBBox())
        exposure.setDetector(detector)

        pixelSizeMm = exposure.getDetector().getPixelSize()[0]

        pixX0 = exposure.getX0()
        pixY0 = exposure.getY0()
        pixX1 = pixX0 + exposure.getWidth() - 1
        pixY1 = pixY0 + exposure.getHeight() - 1

        fpCenter = exposure.getDetector().getCenter(FOCAL_PLANE)
        fpCenterX = fpCenter[0]
        fpCenterY = fpCenter[1]
        pixCenter = exposure.getDetector().getCenter(PIXELS)

        # Create an instance of IsrTask
        task = IsrTask()

        # Make a polygon that encompases entire ccd (radius of 2*max of width/height)
        fpRadius = 2.0*max(exposure.getWidth()*pixelSizeMm, exposure.getHeight()*pixelSizeMm)
        fpPolygon = makeCircularPolygon(fpCenterX, fpCenterY, fpRadius, numPolygonPoints)
        # Set the polygon that is the intersection of fpPolygon and ccd
        task.setValidPolygonIntersect(exposure, fpPolygon)
        # Since the ccd is fully contained in the fpPolygon, the intersection should be the ccdPolygon itself
        ccdPolygonPix = afwGeom.Polygon(exposure.getDetector().getCorners(PIXELS))
        self.assertEqual(exposure.getInfo().getValidPolygon(), ccdPolygonPix)

        # Make a polygon that is entirely within, but smaller than, the ccd
        # (radius of 0.2*min of width/height)
        fpRadius = 0.2*min(exposure.getWidth()*pixelSizeMm, exposure.getHeight()*pixelSizeMm)
        fpPolygon = makeCircularPolygon(fpCenterX, fpCenterY, fpRadius, numPolygonPoints)
        # Set the polygon that is the intersection of fpPolygon and ccd
        task.setValidPolygonIntersect(exposure, fpPolygon)
        # all vertices of polygon should be contained within the ccd
        for x in exposure.getInfo().getValidPolygon():
            self.assertTrue(ccdPolygonPix.contains(lsst.geom.Point2D(x)))
        # intersection is smaller than the ccd
        self.assertNotEqual(exposure.getInfo().getValidPolygon(), ccdPolygonPix)

        # make a simple square polygon that partly intersects the ccd, centered at ccd center
        fpPolygonSize = max(exposure.getWidth()*pixelSizeMm, exposure.getHeight()*pixelSizeMm)
        fpPolygon = makeSquarePolygon(fpCenterX, fpCenterY, fpPolygonSize)
        task.setValidPolygonIntersect(exposure, fpPolygon)
        # Check that the polygon contains the central pixel (offset by one to actually be "contained")
        pixCenterPlusOne = lsst.geom.Point2D(pixCenter[0] + 1, pixCenter[1] + 1)
        self.assertTrue(exposure.getInfo().getValidPolygon().contains(lsst.geom.Point2D(pixCenterPlusOne)))
        # Check that the polygon contains the upper right ccd edge
        self.assertTrue(exposure.getInfo().getValidPolygon().contains(lsst.geom.Point2D(pixX1, pixY1)))
    def testSetPolygonIntersect(self):
        # Create a detector
        detector = DetectorWrapper().detector
        numPolygonPoints = 50
        # Create an exposure with bounding box defined by detector
        exposure = afwImage.ExposureF(detector.getBBox())
        exposure.setDetector(detector)

        pixelSizeMm = exposure.getDetector().getPixelSize()[0]

        pixX0 = exposure.getX0()
        pixY0 = exposure.getY0()
        pixX1 = pixX0 + exposure.getWidth() - 1
        pixY1 = pixY0 + exposure.getHeight() - 1

        fpCenter = exposure.getDetector().getCenter(FOCAL_PLANE)
        fpCenterX = fpCenter[0]
        fpCenterY = fpCenter[1]
        pixCenter = exposure.getDetector().getCenter(PIXELS)

        # Create an instance of IsrTask
        task = IsrTask()

        # Make a polygon that encompases entire ccd (radius of 2*max of width/height)
        fpRadius = 2.0*max(exposure.getWidth()*pixelSizeMm, exposure.getHeight()*pixelSizeMm)
        fpPolygon = makeCircularPolygon(fpCenterX, fpCenterY, fpRadius, numPolygonPoints)
        # Set the polygon that is the intersection of fpPolygon and ccd
        task.setValidPolygonIntersect(exposure, fpPolygon)
        # Since the ccd is fully contained in the fpPolygon, the intersection should be the ccdPolygon itself
        ccdPolygonPix = afwGeom.Polygon(exposure.getDetector().getCorners(PIXELS))
        self.assertEqual(exposure.getInfo().getValidPolygon(), ccdPolygonPix)

        # Make a polygon that is entirely within, but smaller than, the ccd
        # (radius of 0.2*min of width/height)
        fpRadius = 0.2*min(exposure.getWidth()*pixelSizeMm, exposure.getHeight()*pixelSizeMm)
        fpPolygon = makeCircularPolygon(fpCenterX, fpCenterY, fpRadius, numPolygonPoints)
        # Set the polygon that is the intersection of fpPolygon and ccd
        task.setValidPolygonIntersect(exposure, fpPolygon)
        # all vertices of polygon should be contained within the ccd
        for x in exposure.getInfo().getValidPolygon():
            self.assertTrue(ccdPolygonPix.contains(afwGeom.Point2D(x)))
        # intersection is smaller than the ccd
        self.assertNotEqual(exposure.getInfo().getValidPolygon(), ccdPolygonPix)

        # make a simple square polygon that partly intersects the ccd, centered at ccd center
        fpPolygonSize = max(exposure.getWidth()*pixelSizeMm, exposure.getHeight()*pixelSizeMm)
        fpPolygon = makeSquarePolygon(fpCenterX, fpCenterY, fpPolygonSize)
        task.setValidPolygonIntersect(exposure, fpPolygon)
        # Check that the polygon contains the central pixel (offset by one to actually be "contained")
        pixCenterPlusOne = afwGeom.Point2D(pixCenter[0] + 1, pixCenter[1] + 1)
        self.assertTrue(exposure.getInfo().getValidPolygon().contains(afwGeom.Point2D(pixCenterPlusOne)))
        # Check that the polygon contains the upper right ccd edge
        self.assertTrue(exposure.getInfo().getValidPolygon().contains(afwGeom.Point2D(pixX1, pixY1)))
Example #9
0
    def testCopyDetector(self):
        """Test copyDetector() method
        """
        #
        # Make a copy without any modifications
        #
        detector = DetectorWrapper().detector
        ndetector = cameraGeom.copyDetector(detector)

        self.assertEqual(detector.getName(), ndetector.getName())
        self.assertEqual(detector.getBBox(), ndetector.getBBox())
        self.assertEqual(detector.getPhysicalType(),
                         ndetector.getPhysicalType())
        for amp, namp in zip(detector, ndetector):
            self.assertEqual(amp.getBBox(), namp.getBBox())
            self.assertEqual(amp.getRawXYOffset(), namp.getRawXYOffset())
        #
        # Now make a copy with a hacked-up set of amps
        #
        ampInfoCatalog = detector.getAmpInfoCatalog().copy(deep=True)
        for i, amp in enumerate(ampInfoCatalog, 1):
            amp.setRawXYOffset(i * lsst.geom.ExtentI(1, 1))

        ndetector = cameraGeom.copyDetector(detector,
                                            ampInfoCatalog=ampInfoCatalog)

        self.assertEqual(detector.getName(), ndetector.getName())
        self.assertEqual(detector.getBBox(), ndetector.getBBox())
        for i, (amp, namp) in enumerate(zip(detector, ndetector), 1):
            self.assertEqual(amp.getBBox(), namp.getBBox())
            self.assertNotEqual(amp.getRawXYOffset(), namp.getRawXYOffset())
            self.assertEqual(namp.getRawXYOffset()[0], i)
Example #10
0
    def testConstructorErrors(self):
        """Test constructor errors
        """
        def duplicateAmpName(dw):
            """Set two amplifiers to the same name"""
            dw.ampInfo[1].setName(dw.ampInfo[0].getName())
        with self.assertRaises(lsst.pex.exceptions.Exception):
            DetectorWrapper(modFunc=duplicateAmpName)

        def addBadCameraSys(dw):
            """Add an invalid camera system"""
            dw.transMap[cameraGeom.CameraSys("foo", "wrong detector")] = \
                afwGeom.IdentityXYTransform()
        with self.assertRaises(lsst.pex.exceptions.Exception):
            DetectorWrapper(modFunc=addBadCameraSys)
    def setUp(self):

        nSources = 10
        # CFHT Filters from the camera mapper.
        afwImageUtils.resetFilters()
        afwImageUtils.defineFilter('u', lambdaEff=374, alias="u.MP9301")
        afwImageUtils.defineFilter('g', lambdaEff=487, alias="g.MP9401")
        afwImageUtils.defineFilter('r', lambdaEff=628, alias="r.MP9601")
        afwImageUtils.defineFilter('i', lambdaEff=778, alias="i.MP9701")
        afwImageUtils.defineFilter('z', lambdaEff=1170, alias="z.MP9801")

        self.bbox = geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(1024, 1153))
        dataset = measTests.TestDataset(self.bbox)
        for srcIdx in range(nSources):
            dataset.addSource(100000.0, geom.Point2D(100, 100))
        self.inputCatalogNoFlags, _ = make_input_source_catalog(dataset, False)
        self.inputCatalog, self.exposure = \
            make_input_source_catalog(dataset, True)

        detector = DetectorWrapper(id=23,
                                   bbox=self.exposure.getBBox()).detector
        visit = afwImage.VisitInfo(exposureId=4321,
                                   exposureTime=200.,
                                   date=dafBase.DateTime(nsecs=1400000000 *
                                                         10**9))
        self.exposure.setDetector(detector)
        self.exposure.getInfo().setVisitInfo(visit)
        self.exposure.setFilter(afwImage.Filter('g.MP9401'))
        scale = 2
        scaleErr = 1
        self.photoCalib = afwImage.PhotoCalib(scale, scaleErr)
        self.exposure.setPhotoCalib(self.photoCalib)
Example #12
0
 def testMultiPlaneFitsReaders(self):
     """Run tests for MaskedImageFitsReader and ExposureFitsReader.
     """
     metadata = PropertyList()
     metadata.add("FIVE", 5)
     metadata.add("SIX", 6.0)
     wcs = makeSkyWcs(Point2D(2.5, 3.75),
                      SpherePoint(40.0 * degrees, 50.0 * degrees),
                      np.array([[1E-5, 0.0], [0.0, -1E-5]]))
     defineFilter("test_readers_filter", lambdaEff=470.0)
     calib = PhotoCalib(2.5E4)
     psf = GaussianPsf(21, 21, 8.0)
     polygon = Polygon(Box2D(self.bbox))
     apCorrMap = ApCorrMap()
     visitInfo = VisitInfo(exposureTime=5.0)
     transmissionCurve = TransmissionCurve.makeIdentity()
     coaddInputs = CoaddInputs(ExposureTable.makeMinimalSchema(),
                               ExposureTable.makeMinimalSchema())
     detector = DetectorWrapper().detector
     record = coaddInputs.ccds.addNew()
     record.setWcs(wcs)
     record.setPhotoCalib(calib)
     record.setPsf(psf)
     record.setValidPolygon(polygon)
     record.setApCorrMap(apCorrMap)
     record.setVisitInfo(visitInfo)
     record.setTransmissionCurve(transmissionCurve)
     record.setDetector(detector)
     for n, dtypeIn in enumerate(self.dtypes):
         with self.subTest(dtypeIn=dtypeIn):
             exposureIn = Exposure(self.bbox, dtype=dtypeIn)
             shape = exposureIn.image.array.shape
             exposureIn.image.array[:, :] = np.random.randint(low=1,
                                                              high=5,
                                                              size=shape)
             exposureIn.mask.array[:, :] = np.random.randint(low=1,
                                                             high=5,
                                                             size=shape)
             exposureIn.variance.array[:, :] = np.random.randint(low=1,
                                                                 high=5,
                                                                 size=shape)
             exposureIn.setMetadata(metadata)
             exposureIn.setWcs(wcs)
             exposureIn.setFilter(Filter("test_readers_filter"))
             exposureIn.setFilterLabel(
                 FilterLabel(physical="test_readers_filter"))
             exposureIn.setPhotoCalib(calib)
             exposureIn.setPsf(psf)
             exposureIn.getInfo().setValidPolygon(polygon)
             exposureIn.getInfo().setApCorrMap(apCorrMap)
             exposureIn.getInfo().setVisitInfo(visitInfo)
             exposureIn.getInfo().setTransmissionCurve(transmissionCurve)
             exposureIn.getInfo().setCoaddInputs(coaddInputs)
             exposureIn.setDetector(detector)
             with lsst.utils.tests.getTempFilePath(".fits") as fileName:
                 exposureIn.writeFits(fileName)
                 self.checkMaskedImageFitsReader(exposureIn, fileName,
                                                 self.dtypes[n:])
                 self.checkExposureFitsReader(exposureIn, fileName,
                                              self.dtypes[n:])
Example #13
0
    def testTransform(self):
        """Test the transform method
        """
        dw = DetectorWrapper()
        pixOffset = dw.orientation.getReferencePoint()
        for xyMM in ((25.6, -31.07), (0, 0), (-1.234e5, 3.123e4)):
            fpPoint = afwGeom.Point2D(*xyMM)
            fpCamPoint = cameraGeom.CameraPoint(fpPoint,
                                                cameraGeom.FOCAL_PLANE)
            pixCamPoint = dw.detector.transform(fpCamPoint, cameraGeom.PIXELS)
            pixPoint = pixCamPoint.getPoint()
            for i in range(2):
                self.assertAlmostEquals(
                    fpPoint[i] / dw.pixelSize[i] + pixOffset[i], pixPoint[i])
            fpCamPoint2 = dw.detector.transform(pixCamPoint,
                                                cameraGeom.FOCAL_PLANE)
            fpPoint2 = fpCamPoint2.getPoint()
            for i in range(2):
                self.assertAlmostEquals(fpPoint[i], fpPoint2[i])

            # test pix to pix
            pixCamPoint2 = dw.detector.transform(pixCamPoint,
                                                 cameraGeom.PIXELS)
            for i in range(2):
                self.assertAlmostEquals(pixCamPoint.getPoint()[i],
                                        pixCamPoint2.getPoint()[i])

        # make sure you cannot transform to a different detector
        pixCamPoint = dw.detector.makeCameraPoint(afwGeom.Point2D(1, 1),
                                                  cameraGeom.PIXELS)
        otherCamSys = cameraGeom.CameraSys(cameraGeom.PIXELS, "other detector")
        with self.assertRaises(lsst.pex.exceptions.Exception):
            dw.detector.transform(pixCamPoint, otherCamSys)
Example #14
0
    def setUp(self):
        super().setUp()

        afwImage.Filter.reset()
        afwImage.FilterProperty.reset()
        defineFilter("g", 470.0)

        self.wcs = afwGeom.makeSkyWcs(
            lsst.geom.Point2D(0.0, 0.0),
            lsst.geom.SpherePoint(2.0, 34.0, lsst.geom.degrees),
            np.identity(2),
        )
        self.photoCalib = afwImage.PhotoCalib(1.5)
        self.psf = DummyPsf(2.0)
        self.detector = DetectorWrapper().detector
        self.summaryStats = afwImage.ExposureSummaryStats(ra=100.0)
        self.polygon = afwGeom.Polygon(
            lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
                            lsst.geom.Point2D(25.0, 20.0)))
        self.coaddInputs = afwImage.CoaddInputs()
        self.apCorrMap = afwImage.ApCorrMap()
        self.transmissionCurve = afwImage.TransmissionCurve.makeIdentity()

        self.exposureInfo = afwImage.ExposureInfo()
        gFilter = afwImage.Filter("g")
        gFilterLabel = afwImage.FilterLabel(band="g")
        self.exposureInfo.setFilter(gFilter)
        self.exposureInfo.setFilterLabel(gFilterLabel)
Example #15
0
    def testConstructorErrors(self):
        """Test constructor errors
        """
        def duplicateAmpName(dw):
            """Set two amplifiers to the same name"""
            dw.ampInfo[1].setName(dw.ampInfo[0].getName())

        with self.assertRaises(lsst.pex.exceptions.Exception):
            DetectorWrapper(modFunc=duplicateAmpName)

        # These break in the pybind layer
        for crosstalk in (
            [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],  # 1D and not numpy
                np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0,
                          9.0]),  # 1D, wrong numpy type
                np.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
                         dtype=np.float32),  # 1D
        ):
            self.assertRaises(TypeError, DetectorWrapper, crosstalk=crosstalk)
        # These break in the Detector ctor: wrong shape
        self.assertRaises(lsst.pex.exceptions.InvalidParameterError,
                          DetectorWrapper,
                          crosstalk=np.array([[1.0, 2.0], [3.0, 4.0]]))
        self.assertRaises(lsst.pex.exceptions.InvalidParameterError,
                          DetectorWrapper,
                          crosstalk=np.array([[1.0, 2.0], [3.0, 4.0],
                                              [5.0, 6.0]]))
Example #16
0
    def testTransform(self):
        """Test the transform method
        """
        dw = DetectorWrapper()
        pixOffset = dw.orientation.getReferencePoint()
        for xyMM in ((25.6, -31.07), (0, 0), (-1.234e5, 3.123e4)):
            fpPoint = afwGeom.Point2D(*xyMM)
            pixPoint = dw.detector.transform(fpPoint, cameraGeom.FOCAL_PLANE,
                                             cameraGeom.PIXELS)
            for i in range(2):
                self.assertAlmostEqual(
                    fpPoint[i] / dw.pixelSize[i] + pixOffset[i], pixPoint[i])
            fpPoint2 = dw.detector.transform(pixPoint, cameraGeom.PIXELS,
                                             cameraGeom.FOCAL_PLANE)
            self.assertPairsAlmostEqual(fpPoint, fpPoint2)

            # test pix to pix
            pixPoint2 = dw.detector.transform(pixPoint, cameraGeom.PIXELS,
                                              cameraGeom.PIXELS)
            self.assertPairsAlmostEqual(pixPoint, pixPoint2)

        # make sure you cannot transform to or from a different detector
        otherCamSys = cameraGeom.CameraSys(cameraGeom.PIXELS, "other detector")
        for goodSys in (cameraGeom.PIXELS, cameraGeom.FOCAL_PLANE):
            with self.assertRaises(lsst.pex.exceptions.Exception):
                dw.detector.transform(pixPoint, goodSys, otherCamSys)
            with self.assertRaises(lsst.pex.exceptions.Exception):
                dw.detector.transform(pixPoint, otherCamSys, goodSys)
Example #17
0
 def test_makeWcs_fail_if_detector_is_bad(self):
     """If Detector is broken, raise an exception.
     """
     # This detector doesn't know about FIELD_ANGLE, so can't be used to
     # make a SkyWcs.
     detector = DetectorWrapper().detector
     with self.assertRaises(InitialSkyWcsError):
         self.formatter.makeWcs(self.visitInfo, detector)
Example #18
0
    def testCopyDetector(self):
        """Test copyDetector() method
        """
        #
        # Make a copy without any modifications
        #
        detector = DetectorWrapper().detector
        ndetector = cameraGeom.copyDetector(detector)

        self.assertEqual(detector.getName(), ndetector.getName())
        self.assertEqual(detector.getBBox(), ndetector.getBBox())
        self.assertEqual(detector.getPhysicalType(), ndetector.getPhysicalType())
        for amp, namp in zip(detector, ndetector):
            self.assertEqual(amp.getBBox(), namp.getBBox())
            self.assertEqual(amp.getRawXYOffset(), namp.getRawXYOffset())
        #
        # Now make a copy with a hacked-up set of amps
        #
        ampInfoCatalog = detector.getAmpInfoCatalog().copy(deep=True)
        for i, amp in enumerate(ampInfoCatalog, 1):
            amp.setRawXYOffset(i*lsst.geom.ExtentI(1, 1))

        ndetector = cameraGeom.copyDetector(
            detector, ampInfoCatalog=ampInfoCatalog)

        self.assertEqual(detector.getName(), ndetector.getName())
        self.assertEqual(detector.getBBox(), ndetector.getBBox())
        for i, (amp, namp) in enumerate(zip(detector, ndetector), 1):
            self.assertEqual(amp.getBBox(), namp.getBBox())
            self.assertNotEqual(amp.getRawXYOffset(), namp.getRawXYOffset())
            self.assertEqual(namp.getRawXYOffset()[0], i)
Example #19
0
 def testPersistence(self):
     """Test round-tripping a Detector through FITS I/O.
     """
     dw = DetectorWrapper()
     detectorIn = dw.detector
     with lsst.utils.tests.getTempFilePath("*.fits") as filename:
         detectorIn.writeFits(filename)
         detectorOut = cameraGeom.Detector.readFits(filename)
     self.assertDetectorsEqual(detectorIn, detectorOut)
Example #20
0
    def testTransformAccess(self):
        """Test hasTransform and getTransform
        """
        detector = DetectorWrapper().detector
        for camSys in (cameraGeom.FOCAL_PLANE, cameraGeom.PIXELS, cameraGeom.TAN_PIXELS):
            # camSys may be a CameraSys or a CameraSysPrefix
            fullCamSys = detector.makeCameraSys(camSys)
            self.assertTrue(detector.hasTransform(camSys))
            self.assertTrue(detector.hasTransform(fullCamSys))
            detector.getTransform(camSys)
            detector.getTransform(fullCamSys)

        for badCamSys in (
            cameraGeom.CameraSys("badName"),
            cameraGeom.CameraSys("pixels", "badDetectorName")
        ):
            self.assertFalse(detector.hasTransform(badCamSys))
            self.assertRaises(lsst.pex.exceptions.Exception, detector.getTransform, badCamSys)
Example #21
0
    def __init__(self,
                 shape=geom.Extent2I(201, 301),
                 offset=geom.Point2I(-123, -45),
                 backgroundLevel=314.592,
                 seed=42,
                 nSrc=37,
                 fluxRange=2.,
                 noiseLevel=5,
                 sourceSigma=200.,
                 minPsfSize=1.5,
                 maxPsfSize=3.,
                 pixelScale=0.2 * arcseconds,
                 ra=209. * degrees,
                 dec=-20.25 * degrees,
                 ccd=37,
                 patch=42,
                 patchGen2="2,3",
                 tract=0):
        self.ra = ra
        self.dec = dec
        self.pixelScale = pixelScale
        self.patch = patch
        self.patchGen2 = patchGen2
        self.tract = tract
        self.filterLabel = afwImage.FilterLabel(band="gTest", physical="gTest")
        self.rngData = np.random.default_rng(seed)
        self.rngMods = np.random.default_rng(seed + 1)
        self.bbox = geom.Box2I(offset, shape)
        if not self.bbox.contains(0, 0):
            raise ValueError(
                f"The bounding box must contain the coordinate (0, 0). {repr(self.bbox)}"
            )
        self.wcs = self.makeDummyWcs()

        # Set up properties of the simulations
        nSigmaForKernel = 5
        self.kernelSize = (int(maxPsfSize * nSigmaForKernel + 0.5) //
                           2) * 2 + 1  # make sure it is odd

        bufferSize = self.kernelSize // 2
        x0, y0 = self.bbox.getBegin()
        xSize, ySize = self.bbox.getDimensions()
        # Set the pixel coordinates and fluxes of the simulated sources.
        self.xLoc = self.rngData.random(nSrc) * (
            xSize - 2 * bufferSize) + bufferSize + x0
        self.yLoc = self.rngData.random(nSrc) * (
            ySize - 2 * bufferSize) + bufferSize + y0
        self.flux = (self.rngData.random(nSrc) *
                     (fluxRange - 1.) + 1.) * sourceSigma * noiseLevel

        self.backgroundLevel = backgroundLevel
        self.noiseLevel = noiseLevel
        self.minPsfSize = minPsfSize
        self.maxPsfSize = maxPsfSize
        self.detector = DetectorWrapper(name=f"detector {ccd}",
                                        id=ccd).detector
Example #22
0
 def testGetCenter(self):
     """Test the getCenter method
     """
     dw = DetectorWrapper()
     ctrPixPoint = lsst.geom.Box2D(dw.detector.getBBox()).getCenter()
     for cameraSys in (cameraGeom.FOCAL_PLANE, cameraGeom.PIXELS):
         ctrPoint = dw.detector.getCenter(cameraSys)
         transform = dw.detector.getTransform(cameraGeom.PIXELS, cameraSys)
         predCtrPoint = transform.applyForward(ctrPixPoint)
         self.assertPairsAlmostEqual(predCtrPoint, ctrPoint)
Example #23
0
 def testIteration(self):
     """Test iteration over amplifiers and __getitem__
     """
     dw = DetectorWrapper()
     ampList = [amp for amp in dw.detector]
     self.assertEquals(len(ampList), len(dw.ampInfo))
     for i, amp in enumerate(ampList):
         self.assertEquals(amp.getName(), dw.detector[i].getName())
         self.assertEquals(amp.getName(), dw.ampInfo[i].getName())
         self.assertEquals(amp.getName(), dw.detector[amp.getName()].getName())
    def setUp(self):
        np.random.seed(1234)
        self.cutoutSize = 35
        self.center = lsst.geom.Point2D(50.1, 49.8)
        self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(-20, -30),
                                    lsst.geom.Extent2I(140, 160))
        self.dataset = lsst.meas.base.tests.TestDataset(self.bbox)
        self.dataset.addSource(100000.0, self.center)
        exposure, catalog = self.dataset.realize(
            10.0, self.dataset.makeMinimalSchema(), randomSeed=0)
        self.exposure = exposure
        detector = DetectorWrapper(id=23, bbox=exposure.getBBox()).detector
        self.exposure.setDetector(detector)

        visit = afwImage.VisitInfo(exposureId=1234,
                                   exposureTime=200.,
                                   date=dafBase.DateTime(
                                       "2014-05-13T17:00:00.000000000",
                                       dafBase.DateTime.Timescale.TAI))
        self.exposure.getInfo().setVisitInfo(visit)

        self.filter_names = ["g"]
        afwImageUtils.resetFilters()
        afwImageUtils.defineFilter('g', lambdaEff=487, alias="g.MP9401")
        self.exposure.setFilter(afwImage.Filter('g'))

        diaObjects = makeDiaObjects(2, self.exposure)
        diaSourceHistory = makeDiaSources(10, diaObjects["diaObjectId"],
                                          self.exposure)
        diaForcedSources = makeDiaForcedSources(10, diaObjects["diaObjectId"],
                                                self.exposure)
        self.diaObjects, diaSourceHistory, self.diaForcedSources = _roundTripThroughApdb(
            diaObjects, diaSourceHistory, diaForcedSources,
            self.exposure.getInfo().getVisitInfo().getDate().toPython())
        self.diaObjects.replace(to_replace=[None], value=np.nan, inplace=True)
        diaSourceHistory.replace(to_replace=[None], value=np.nan, inplace=True)
        self.diaForcedSources.replace(to_replace=[None],
                                      value=np.nan,
                                      inplace=True)
        diaSourceHistory["programId"] = 0

        self.diaSources = diaSourceHistory.loc[[(0, "g", 8), (1, "g", 9)], :]
        self.diaSources["bboxSize"] = self.cutoutSize
        self.diaSourceHistory = diaSourceHistory.drop(labels=[(0, "g",
                                                               8), (1, "g",
                                                                    9)])

        self.cutoutWcs = wcs.WCS(naxis=2)
        self.cutoutWcs.wcs.crpix = [self.center[0], self.center[1]]
        self.cutoutWcs.wcs.crval = [
            self.exposure.getWcs().getSkyOrigin().getRa().asDegrees(),
            self.exposure.getWcs().getSkyOrigin().getDec().asDegrees()
        ]
        self.cutoutWcs.wcs.cd = self.exposure.getWcs().getCdMatrix()
        self.cutoutWcs.wcs.ctype = ["RA---TAN", "DEC--TAN"]
 def setUp(self):
     # Define point2D object which are distributed about a detector
     self.positions = [lsst.afw.geom.Point2D(*x) for x in ((50.1, 49.8), (12, 15.6), (13.4, 100.0))]
     # Define a box which will be used to as boundaries to construct an detector object
     self.bbox = lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(-20, -30),
                                     lsst.afw.geom.Extent2I(140, 160))
     self.dataset = lsst.meas.base.tests.TestDataset(self.bbox)
     # Add in sources to synthetic dataset at defined positions with an arbitrary value
     for pos in self.positions:
         self.dataset.addSource(100000.0, pos)
     self.dw = DetectorWrapper()
Example #26
0
    def setUp(self):
        self.camera = CameraWrapper().camera
        self.detector = DetectorWrapper().detector
        self.crpix = lsst.geom.Point2D(50, 100)
        self.crval = lsst.geom.SpherePoint(36, 71, lsst.geom.degrees)
        scale = 1.0 * lsst.geom.arcseconds
        self.cdMatrix = afwGeom.makeCdMatrix(scale=scale)
        self.wcs = afwGeom.makeSkyWcs(crpix=self.crpix,
                                      crval=self.crval,
                                      cdMatrix=self.cdMatrix)
        self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(-10, 10),
                                    lsst.geom.Extent2I(1000, 1022))
        self.exposure = ExposureF(self.bbox)

        # set the few items of ExposureInfo needed by IsrTask.run
        # when only adding a distortion model
        exposureInfo = ExposureInfo(photoCalib=PhotoCalib(1.0),
                                    detector=self.detector,
                                    visitInfo=VisitInfo(exposureTime=1.0),
                                    wcs=self.wcs)

        self.exposure.setInfo(exposureInfo)
Example #27
0
    def testMakeCameraSys(self):
        """Test the makeCameraSys method
        """
        dw = DetectorWrapper()
        for sysName in ("csys1", "csys2"):
            for detectorName in ("", dw.name, "a different detector"):
                inCamSys = cameraGeom.CameraSys(sysName, detectorName)
                outCamSys = dw.detector.makeCameraSys(inCamSys)
                self.assertEquals(inCamSys, outCamSys)

            inCamSysPrefix = cameraGeom.CameraSysPrefix(sysName)
            outCamSys2 = dw.detector.makeCameraSys(inCamSysPrefix)
            self.assertEquals(outCamSys2, cameraGeom.CameraSys(sysName, dw.name))
    def setUp(self):
        # CFHT Filters from the camera mapper.
        afwImageUtils.resetFilters()
        afwImageUtils.defineFilter('u', lambdaEff=374, alias="u.MP9301")
        afwImageUtils.defineFilter('g', lambdaEff=487, alias="g.MP9401")
        afwImageUtils.defineFilter('r', lambdaEff=628, alias="r.MP9601")
        afwImageUtils.defineFilter('i', lambdaEff=778, alias="i.MP9701")
        afwImageUtils.defineFilter('z', lambdaEff=1170, alias="z.MP9801")

        self.metadata = dafBase.PropertySet()

        self.metadata.set("SIMPLE", "T")
        self.metadata.set("BITPIX", -32)
        self.metadata.set("NAXIS", 2)
        self.metadata.set("NAXIS1", 1024)
        self.metadata.set("NAXIS2", 1153)
        self.metadata.set("RADECSYS", 'FK5')
        self.metadata.set("EQUINOX", 2000.)

        self.metadata.setDouble("CRVAL1", 215.604025685476)
        self.metadata.setDouble("CRVAL2", 53.1595451514076)
        self.metadata.setDouble("CRPIX1", 1109.99981456774)
        self.metadata.setDouble("CRPIX2", 560.018167811613)
        self.metadata.set("CTYPE1", 'RA---SIN')
        self.metadata.set("CTYPE2", 'DEC--SIN')

        self.metadata.setDouble("CD1_1", 5.10808596133527E-05)
        self.metadata.setDouble("CD1_2", 1.85579539217196E-07)
        self.metadata.setDouble("CD2_2", -5.10281493481982E-05)
        self.metadata.setDouble("CD2_1", -8.27440751733828E-07)

        self.wcs = afwGeom.makeSkyWcs(self.metadata)
        self.exposure = afwImage.makeExposure(
            afwImage.makeMaskedImageFromArrays(np.ones((1024, 1153))),
            self.wcs)
        detector = DetectorWrapper(id=23,
                                   bbox=self.exposure.getBBox()).detector
        visit = afwImage.VisitInfo(exposureId=4321,
                                   exposureTime=200.,
                                   date=dafBase.DateTime(nsecs=1400000000 *
                                                         10**9))
        self.exposure.setDetector(detector)
        self.exposure.getInfo().setVisitInfo(visit)
        self.exposure.setFilter(afwImage.Filter('g'))
        scale = 2
        scaleErr = 1
        self.photoCalib = afwImage.PhotoCalib(scale, scaleErr)
        self.exposure.setPhotoCalib(self.photoCalib)

        self.inputCatalogNoFlags = make_input_source_catalog(10, False)
        self.inputCatalog = make_input_source_catalog(10, True)
Example #29
0
 def testGetCorners(self):
     """Test the getCorners method
     """
     dw = DetectorWrapper()
     for cameraSys in (cameraGeom.FOCAL_PLANE, cameraGeom.PIXELS):
         # positions of corners in specified camera system
         cornerList = dw.detector.getCorners(cameraSys)
         for posPixels, posCameraSys in zip(
                 lsst.geom.Box2D(dw.bbox).getCorners(), cornerList):
             pixelsToCameraSys = dw.detector.getTransform(
                 cameraGeom.PIXELS, cameraSys)
             predPosCameraSys = pixelsToCameraSys.applyForward(posPixels)
             self.assertPairsAlmostEqual(predPosCameraSys, posCameraSys)
             if cameraSys == cameraGeom.PIXELS:
                 self.assertPairsAlmostEqual(posPixels, predPosCameraSys)
    def testGainAndReadnoise(self):
        import lsst.afw.image as afwImage
        from lsst.afw.cameraGeom.testUtils import DetectorWrapper
        from lsst.ip.isr import IsrTask

        isrTask = IsrTask()

        detector = DetectorWrapper().detector
        raw = afwImage.ExposureF(detector.getBBox())

        level = 10
        readNoise = 1
        raw.image.set(level)

        amp = detector[0]
        amp.setReadNoise(readNoise)

        for gain in [-1, 0, 0.1, 1]:
            amp.setGain(gain)
            isrTask.updateVariance(raw, amp)
            if gain <= 0:
                gain = 1

        self.assertEqual(raw.variance[0, 0, afwImage.LOCAL], level/gain + readNoise**2)
Example #31
0
 def testGetCenter(self):
     """Test the getCenter method
     """
     dw = DetectorWrapper()
     ctrPixPoint = afwGeom.Box2D(dw.detector.getBBox()).getCenter()
     ctrPixCameraPoint = dw.detector.makeCameraPoint(ctrPixPoint, cameraGeom.PIXELS)
     for cameraSys in (cameraGeom.FOCAL_PLANE, cameraGeom.PIXELS):
         ctrCameraPoint = dw.detector.getCenter(cameraSys)
         self.assertEquals(ctrCameraPoint.getCameraSys().getSysName(), cameraSys.getSysName())
         ctrPoint = ctrCameraPoint.getPoint()
         predCtrCameraPoint = dw.detector.transform(ctrPixCameraPoint, cameraSys)
         predCtrPoint = predCtrCameraPoint.getPoint()
         for i in range(2):
             self.assertAlmostEquals(ctrPoint[i], predCtrPoint[i])
             if cameraSys == cameraGeom.PIXELS:
                 self.assertAlmostEquals(ctrPixPoint[i], ctrPoint[i])
Example #32
0
def get_psf_exp(
    exp,
    coadd_cen_skypos,
    var,
):
    """
    create a psf exposure to be coadded, rendered at the
    position in the exposure corresponding to the center of the
    coadd

    Parameters
    ----------
    exp: afw_image.ExposureF
        The exposure
    coadd_cen_skypos: SpherePoint
        The sky position of the center of the coadd within its
        bbox
    var: float
        The variance to set in the psf variance map

    Returns
    -------
    psf ExposureF
    """

    wcs = exp.getWcs()
    pos = wcs.skyToPixel(coadd_cen_skypos)

    psf_obj = exp.getPsf()
    psf_image = psf_obj.computeImage(pos).array

    psf_dim = psf_image.shape[0]

    psf_bbox = get_psf_bbox(pos=pos, dim=psf_dim)

    # wcs same as SE exposure
    psf_exp = afw_image.ExposureF(psf_bbox, wcs)
    psf_exp.image.array[:, :] = psf_image
    psf_exp.variance.array[:, :] = var
    psf_exp.mask.array[:, :] = 0

    psf_exp.setFilterLabel(exp.getFilterLabel())
    detector = DetectorWrapper().detector
    psf_exp.setDetector(detector)

    return psf_exp
Example #33
0
 def testGetCorners(self):
     """Test the getCorners method
     """
     dw = DetectorWrapper()
     for cameraSys in (cameraGeom.FOCAL_PLANE, cameraGeom.PIXELS):
         cornerList = dw.detector.getCorners(cameraSys)
         for fromPoint, toPoint in itertools.izip(afwGeom.Box2D(dw.bbox).getCorners(), cornerList):
             predToCameraPoint = dw.detector.transform(
                 dw.detector.makeCameraPoint(fromPoint, cameraGeom.PIXELS),
                 cameraSys,
             )
             predToPoint = predToCameraPoint.getPoint()
             self.assertEquals(predToCameraPoint.getCameraSys().getSysName(), cameraSys.getSysName())
             for i in range(2):
                 self.assertAlmostEquals(predToPoint[i], toPoint[i])
                 if cameraSys == cameraGeom.PIXELS:
                     self.assertAlmostEquals(fromPoint[i], toPoint[i])
    def setUp(self):
        self.camera = CameraWrapper().camera
        self.detector = DetectorWrapper().detector
        self.crpix = afwGeom.Point2D(50, 100)
        self.crval = afwGeom.SpherePoint(36, 71, afwGeom.degrees)
        scale = 1.0*afwGeom.arcseconds
        self.cdMatrix = afwGeom.makeCdMatrix(scale=scale)
        self.wcs = afwGeom.makeSkyWcs(crpix=self.crpix, crval=self.crval, cdMatrix=self.cdMatrix)
        self.bbox = afwGeom.Box2I(afwGeom.Point2I(-10, 10), afwGeom.Extent2I(1000, 1022))
        self.exposure = ExposureF(self.bbox)

        # set the few items of ExposureInfo needed by IsrTask.run
        # when only adding a distortion model
        exposureInfo = ExposureInfo(photoCalib=PhotoCalib(1.0),
                                    detector=self.detector,
                                    visitInfo=VisitInfo(exposureTime=1.0),
                                    wcs=self.wcs)

        self.exposure.setInfo(exposureInfo)
Example #35
0
    def setUp(self):
        maskedImage = afwImage.MaskedImageF(inFilePathSmall)
        maskedImageMD = afwImage.readMetadata(inFilePathSmall)

        self.smallExposure = afwImage.ExposureF(inFilePathSmall)
        self.width =  maskedImage.getWidth()
        self.height = maskedImage.getHeight()
        self.wcs = afwImage.makeWcs(maskedImageMD)
        self.psf = DummyPsf(2.0)
        self.detector = DetectorWrapper().detector

        self.exposureBlank = afwImage.ExposureF()
        self.exposureMiOnly = afwImage.makeExposure(maskedImage)
        self.exposureMiWcs = afwImage.makeExposure(maskedImage, self.wcs)
        self.exposureCrWcs = afwImage.ExposureF(100, 100, self.wcs)         # n.b. the (100, 100, ...) form
        self.exposureCrOnly = afwImage.ExposureF(afwGeom.ExtentI(100, 100)) # test with ExtentI(100, 100) too

        afwImage.Filter.reset()
        afwImage.FilterProperty.reset()

        filterPolicy = pexPolicy.Policy()
        filterPolicy.add("lambdaEff", 470.0)
        afwImage.Filter.define(afwImage.FilterProperty("g", filterPolicy))
Example #36
0
    def setUp(self):
        maskedImage = afwImage.MaskedImageF(inFilePathSmall)
        maskedImageMD = readMetadata(inFilePathSmall)

        self.smallExposure = afwImage.ExposureF(inFilePathSmall)
        self.width = maskedImage.getWidth()
        self.height = maskedImage.getHeight()
        self.wcs = afwGeom.makeSkyWcs(maskedImageMD, False)
        self.md = maskedImageMD
        self.psf = DummyPsf(2.0)
        self.detector = DetectorWrapper().detector

        self.exposureBlank = afwImage.ExposureF()
        self.exposureMiOnly = afwImage.makeExposure(maskedImage)
        self.exposureMiWcs = afwImage.makeExposure(maskedImage, self.wcs)
        # n.b. the (100, 100, ...) form
        self.exposureCrWcs = afwImage.ExposureF(100, 100, self.wcs)
        # test with ExtentI(100, 100) too
        self.exposureCrOnly = afwImage.ExposureF(lsst.geom.ExtentI(100, 100))

        afwImage.Filter.reset()
        afwImage.FilterProperty.reset()

        defineFilter("g", 470.0)
Example #37
0
class ExposureTestCase(unittest.TestCase):
    """
    A test case for the Exposure Class
    """

    def setUp(self):
        maskedImage = afwImage.MaskedImageF(inFilePathSmall)
        maskedImageMD = afwImage.readMetadata(inFilePathSmall)

        self.smallExposure = afwImage.ExposureF(inFilePathSmall)
        self.width =  maskedImage.getWidth()
        self.height = maskedImage.getHeight()
        self.wcs = afwImage.makeWcs(maskedImageMD)
        self.psf = DummyPsf(2.0)
        self.detector = DetectorWrapper().detector

        self.exposureBlank = afwImage.ExposureF()
        self.exposureMiOnly = afwImage.makeExposure(maskedImage)
        self.exposureMiWcs = afwImage.makeExposure(maskedImage, self.wcs)
        self.exposureCrWcs = afwImage.ExposureF(100, 100, self.wcs)         # n.b. the (100, 100, ...) form
        self.exposureCrOnly = afwImage.ExposureF(afwGeom.ExtentI(100, 100)) # test with ExtentI(100, 100) too

        afwImage.Filter.reset()
        afwImage.FilterProperty.reset()

        filterPolicy = pexPolicy.Policy()
        filterPolicy.add("lambdaEff", 470.0)
        afwImage.Filter.define(afwImage.FilterProperty("g", filterPolicy))
            
    def tearDown(self):
        del self.smallExposure
        del self.wcs
        del self.psf
        del self.detector

        del self.exposureBlank
        del self.exposureMiOnly
        del self.exposureMiWcs
        del self.exposureCrWcs
        del self.exposureCrOnly

    def testGetMaskedImage(self):
        """
        Test to ensure a MaskedImage can be obtained from each
        Exposure. An Exposure is required to have a MaskedImage,
        therefore each of the Exposures should return a MaskedImage.

        MaskedImage class should throw appropriate
        lsst::pex::exceptions::NotFound if the MaskedImage can not be
        obtained.
        """
        maskedImageBlank = self.exposureBlank.getMaskedImage()
        blankWidth = maskedImageBlank.getWidth()
        blankHeight = maskedImageBlank.getHeight()
        if blankWidth != blankHeight != 0:
            self.fail("%s = %s != 0" % (blankWidth, blankHeight))
        
        maskedImageMiOnly = self.exposureMiOnly.getMaskedImage()
        miOnlyWidth = maskedImageMiOnly.getWidth()
        miOnlyHeight = maskedImageMiOnly.getHeight()
        self.assertAlmostEqual(miOnlyWidth, self.width)
        self.assertAlmostEqual(miOnlyHeight, self.height)
        
        # NOTE: Unittests for Exposures created from a MaskedImage and
        # a WCS object are incomplete.  No way to test the validity of
        # the WCS being copied/created.
        
        maskedImageMiWcs = self.exposureMiWcs.getMaskedImage()
        miWcsWidth = maskedImageMiWcs.getWidth()
        miWcsHeight = maskedImageMiWcs.getHeight()
        self.assertAlmostEqual(miWcsWidth, self.width)
        self.assertAlmostEqual(miWcsHeight, self.height)
       
        maskedImageCrWcs = self.exposureCrWcs.getMaskedImage()
        crWcsWidth = maskedImageCrWcs.getWidth()
        crWcsHeight = maskedImageCrWcs.getHeight()
        if crWcsWidth != crWcsHeight != 0:
            self.fail("%s != %s != 0" % (crWcsWidth, crWcsHeight))
        
        maskedImageCrOnly = self.exposureCrOnly.getMaskedImage()
        crOnlyWidth = maskedImageCrOnly.getWidth()
        crOnlyHeight = maskedImageCrOnly.getHeight()
        if crOnlyWidth != crOnlyHeight != 0:
            self.fail("%s != %s != 0" % (crOnlyWidth, crOnlyHeight))

        # Check Exposure.getWidth() returns the MaskedImage's width
        self.assertEqual(crOnlyWidth, self.exposureCrOnly.getWidth())
        self.assertEqual(crOnlyHeight, self.exposureCrOnly.getHeight())

    def testGetWcs(self):
        """
        Test if a WCS can be obtained from each Exposure created with
        a WCS.
    
        Test that appropriate exceptions are thrown if a WCS is
        requested from an Exposure that was not created with a WCS.
        Python turns the pex::exceptions in the Exposure and
        MaskedImage classes into IndexErrors.

        The exposureBlank, exposureMiOnly, and exposureCrOnly
        Exposures should throw a lsst::pex::exceptions::NotFound.
        """

        self.assertTrue(not self.exposureBlank.getWcs())
        self.assertTrue(not self.exposureMiOnly.getWcs())

        # These two should pass
        self.exposureMiWcs.getWcs()
        self.exposureCrWcs.getWcs()
       
        self.assertTrue(not self.exposureCrOnly.getWcs())
            
    def testSetMembers(self):
        """
        Test that the MaskedImage and the WCS of an Exposure can be set.
        """
        exposure = afwImage.ExposureF()

        maskedImage = afwImage.MaskedImageF(inFilePathSmall)
        exposure.setMaskedImage(maskedImage)
        exposure.setWcs(self.wcs)
        exposure.setDetector(self.detector)
        exposure.setFilter(afwImage.Filter("g"))

        self.assertEquals(exposure.getDetector().getName(), self.detector.getName())
        self.assertEquals(exposure.getDetector().getSerial(), self.detector.getSerial())
        self.assertEquals(exposure.getFilter().getName(), "g")
        
        try:
            exposure.getWcs()
        except pexExcept.Exception as e:
            print "caught expected exception (getWcs): %s" % e
            pass
        #
        # Test the Calib member.  The Calib tests are in color.py, here we just check that it's in Exposure
        #
        calib = exposure.getCalib()
        dt = 10
        calib.setExptime(dt)
        self.assertEqual(exposure.getCalib().getExptime(), dt)
        #
        # now check that we can set Calib
        #
        calib = afwImage.Calib()
        dt = 666
        calib.setExptime(dt)

        exposure.setCalib(calib)

        self.assertEqual(exposure.getCalib().getExptime(), dt)
        #
        # Psfs next
        #
        self.assertFalse(exposure.hasPsf())
        exposure.setPsf(self.psf)
        self.assertTrue(exposure.hasPsf())

        exposure.setPsf(DummyPsf(1.0)) # we can reset the Psf
         
        # Test that we can set the MaskedImage and WCS of an Exposure
        # that already has both
        self.exposureMiWcs.setMaskedImage(maskedImage)
        exposure.setWcs(self.wcs)
       
    def testHasWcs(self):
        """
        Test if an Exposure has a WCS or not.
        """
        self.assertFalse(self.exposureBlank.hasWcs())

        self.assertFalse(self.exposureMiOnly.hasWcs())
        self.assertTrue(self.exposureMiWcs.hasWcs())
        self.assertTrue(self.exposureCrWcs.hasWcs())
        self.assertFalse(self.exposureCrOnly.hasWcs())
       
    def testGetSubExposure(self):
        """
        Test that a subExposure of the original Exposure can be obtained.

        The MaskedImage class should throw a
        lsst::pex::exceptions::InvalidParameter if the requested
        subRegion is not fully contained within the original
        MaskedImage.
        
        """
        #
        # This subExposure is valid
        #
        subBBox = afwGeom.Box2I(afwGeom.Point2I(40, 50), afwGeom.Extent2I(10, 10))
        subExposure = self.exposureCrWcs.Factory(self.exposureCrWcs, subBBox, afwImage.LOCAL)
        
        self.checkWcs(self.exposureCrWcs, subExposure)

        # this subRegion is not valid and should trigger an exception
        # from the MaskedImage class and should trigger an exception
        # from the WCS class for the MaskedImage 871034p_1_MI.
        
        subRegion3 = afwGeom.Box2I(afwGeom.Point2I(100, 100), afwGeom.Extent2I(10, 10))
        def getSubRegion():
            self.exposureCrWcs.Factory(self.exposureCrWcs, subRegion3, afwImage.LOCAL)

        self.assertRaises(pexExcept.LengthError, getSubRegion)

        # this subRegion is not valid and should trigger an exception
        # from the MaskedImage class only for the MaskedImage small_MI.
        # small_MI (cols, rows) = (256, 256)

        subRegion4 = afwGeom.Box2I(afwGeom.Point2I(250, 250), afwGeom.Extent2I(10, 10))
        def getSubRegion():
            self.exposureCrWcs.Factory(self.exposureCrWcs, subRegion4, afwImage.LOCAL)

        self.assertRaises(pexExcept.LengthError, getSubRegion)

        #check the sub- and parent- exposures are using the same Wcs transformation
        subBBox = afwGeom.Box2I(afwGeom.Point2I(40, 50), afwGeom.Extent2I(10, 10))
        subExposure = self.exposureCrWcs.Factory(self.exposureCrWcs, subBBox, afwImage.LOCAL)
        parentPos = self.exposureCrWcs.getWcs().pixelToSky(0,0)
        
        parentPos = parentPos.getPosition()
        
        subExpPos = subExposure.getWcs().pixelToSky(0,0).getPosition()
        
        for i in range(2):
            self.assertAlmostEqual(parentPos[i], subExpPos[i], 9, "Wcs in sub image has changed")

    def testReadWriteFits(self):
        """Test readFits and writeFits.
        """
        # This should pass without an exception
        mainExposure = afwImage.ExposureF(inFilePathSmall)
        mainExposure.setDetector(self.detector)
        
        subBBox = afwGeom.Box2I(afwGeom.Point2I(10, 10), afwGeom.Extent2I(40, 50))
        subExposure = mainExposure.Factory(mainExposure, subBBox, afwImage.LOCAL)
        self.checkWcs(mainExposure, subExposure)
        det = subExposure.getDetector()
        self.assertTrue(det)
        
        subExposure = afwImage.ExposureF(inFilePathSmall, subBBox, afwImage.LOCAL)
        
        self.checkWcs(mainExposure, subExposure)
        
        # This should throw an exception
        def getExposure():
            afwImage.ExposureF(inFilePathSmallImage)
        
        self.assertRaises(lsst.afw.fits.FitsError, getExposure)
        
        mainExposure.setPsf(self.psf)

        # Make sure we can write without an exception
        mainExposure.getCalib().setExptime(10)
        mainExposure.getCalib().setMidTime(dafBase.DateTime())
        midMjd = mainExposure.getCalib().getMidTime().get()
        fluxMag0, fluxMag0Err = 1e12, 1e10
        mainExposure.getCalib().setFluxMag0(fluxMag0, fluxMag0Err)

        with utilsTests.getTempFilePath(".fits") as tmpFile:
            mainExposure.writeFits(tmpFile)

            readExposure = type(mainExposure)(tmpFile)

            #
            # Check the round-tripping
            #
            self.assertEqual(mainExposure.getFilter().getName(), readExposure.getFilter().getName())

            self.assertEqual(mainExposure.getCalib().getExptime(), readExposure.getCalib().getExptime())
            self.assertEqual(midMjd, readExposure.getCalib().getMidTime().get())
            self.assertEqual((fluxMag0, fluxMag0Err), readExposure.getCalib().getFluxMag0())

            psf = readExposure.getPsf()
            self.assert_(psf is not None)
            dummyPsf = DummyPsf.swigConvert(psf)
            self.assert_(dummyPsf is not None)
            self.assertEqual(dummyPsf.getValue(), self.psf.getValue())

    def checkWcs(self, parentExposure, subExposure):
        """Compare WCS at corner points of a sub-exposure and its parent exposure
           By using the function indexToPosition, we should be able to convert the indices
           (of the four corners (of the sub-exposure)) to positions and use the wcs
           to get the same sky coordinates for each.
        """
        subMI = subExposure.getMaskedImage()
        subDim = subMI.getDimensions()

        # Note: pixel positions must be computed relative to XY0 when working with WCS
        mainWcs = parentExposure.getWcs()
        subWcs = subExposure.getWcs()

        for xSubInd in (0, subDim.getX()-1):
            for ySubInd in (0, subDim.getY()-1):
                mainWcs.pixelToSky(
                    afwImage.indexToPosition(xSubInd),
                    afwImage.indexToPosition(ySubInd),
                )
                subWcs.pixelToSky(
                    afwImage.indexToPosition(xSubInd),
                    afwImage.indexToPosition(ySubInd),
                )

    def cmpExposure(self, e1, e2):
        self.assertEqual(e1.getDetector().getName(), e2.getDetector().getName())
        self.assertEqual(e1.getDetector().getSerial(), e2.getDetector().getSerial())
        self.assertEqual(e1.getFilter().getName(), e2.getFilter().getName())
        xy = afwGeom.Point2D(0, 0)
        self.assertEqual(e1.getWcs().pixelToSky(xy)[0], e2.getWcs().pixelToSky(xy)[0])
        self.assertEqual(e1.getCalib().getExptime(), e2.getCalib().getExptime())
        # check PSF identity
        if not e1.getPsf():
            self.assertFalse(e2.getPsf())
        else:
            psf1 = DummyPsf.swigConvert(e1.getPsf())
            psf2 = DummyPsf.swigConvert(e2.getPsf())
            self.assertEqual(psf1.getValue(), psf2.getValue())

    def testCopyExposure(self):
        """Copy an Exposure (maybe changing type)"""

        exposureU = afwImage.ExposureU(inFilePathSmall)
        exposureU.setWcs(self.wcs)
        exposureU.setDetector(self.detector)
        exposureU.setFilter(afwImage.Filter("g"))
        exposureU.getCalib().setExptime(666)
        exposureU.setPsf(DummyPsf(4.0))

        exposureF = exposureU.convertF()
        self.cmpExposure(exposureF, exposureU)

        nexp = exposureF.Factory(exposureF, False)
        self.cmpExposure(exposureF, nexp)

        # Ensure that the copy was deep.
        # (actually this test is invalid since getDetector() returns a CONST_PTR)
        # cen0 = exposureU.getDetector().getCenterPixel()
        # x0,y0 = cen0
        # det = exposureF.getDetector()
        # det.setCenterPixel(afwGeom.Point2D(999.0, 437.8))
        # self.assertEqual(exposureU.getDetector().getCenterPixel()[0], x0)
        # self.assertEqual(exposureU.getDetector().getCenterPixel()[1], y0)
    
    def testDeepCopyData(self):
        """Make sure a deep copy of an Exposure has its own data (ticket #2625)
        """
        exp = afwImage.ExposureF(6, 7)
        mi = exp.getMaskedImage()
        mi.getImage().set(100)
        mi.getMask().set(5)
        mi.getVariance().set(200)

        expCopy = exp.clone()
        miCopy = expCopy.getMaskedImage()
        miCopy.getImage().set(-50)
        miCopy.getMask().set(2)
        miCopy.getVariance().set(175)

        self.assertTrue(numpy.allclose(miCopy.getImage().getArray(), -50))
        self.assertTrue(numpy.all(miCopy.getMask().getArray() == 2))
        self.assertTrue(numpy.allclose(miCopy.getVariance().getArray(), 175))

        self.assertTrue(numpy.allclose(mi.getImage().getArray(), 100))
        self.assertTrue(numpy.all(mi.getMask().getArray() == 5))
        self.assertTrue(numpy.allclose(mi.getVariance().getArray(), 200))

    
    def testDeepCopySubData(self):
        """Make sure a deep copy of a subregion of an Exposure has its own data (ticket #2625)
        """
        exp = afwImage.ExposureF(6, 7)
        mi = exp.getMaskedImage()
        mi.getImage().set(100)
        mi.getMask().set(5)
        mi.getVariance().set(200)

        bbox = afwGeom.Box2I(afwGeom.Point2I(1,0), afwGeom.Extent2I(5, 4))
        expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
        miCopy = expCopy.getMaskedImage()
        miCopy.getImage().set(-50)
        miCopy.getMask().set(2)
        miCopy.getVariance().set(175)

        self.assertTrue(numpy.allclose(miCopy.getImage().getArray(), -50))
        self.assertTrue(numpy.all(miCopy.getMask().getArray() == 2))
        self.assertTrue(numpy.allclose(miCopy.getVariance().getArray(), 175))

        self.assertTrue(numpy.allclose(mi.getImage().getArray(), 100))
        self.assertTrue(numpy.all(mi.getMask().getArray() == 5))
        self.assertTrue(numpy.allclose(mi.getVariance().getArray(), 200))
    
    def testDeepCopyMetadata(self):
        """Make sure a deep copy of an Exposure has a deep copy of metadata (ticket #2568)
        """
        exp = afwImage.ExposureF(10, 10)
        expMeta = exp.getMetadata()
        expMeta.set("foo", 5)
        expCopy = exp.clone()
        expCopyMeta = expCopy.getMetadata()
        expCopyMeta.set("foo", 6)
        self.assertEqual(expCopyMeta.get("foo"), 6)
        self.assertEqual(expMeta.get("foo"), 5) # this will fail if the bug is present

    def testDeepCopySubMetadata(self):
        """Make sure a deep copy of a subregion of an Exposure has a deep copy of metadata (ticket #2568)
        """
        exp = afwImage.ExposureF(10, 10)
        expMeta = exp.getMetadata()
        expMeta.set("foo", 5)
        bbox = afwGeom.Box2I(afwGeom.Point2I(1,0), afwGeom.Extent2I(5, 5))
        expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
        expCopyMeta = expCopy.getMetadata()
        expCopyMeta.set("foo", 6)
        self.assertEqual(expCopyMeta.get("foo"), 6)
        self.assertEqual(expMeta.get("foo"), 5) # this will fail if the bug is present

    def testMakeExposureLeaks(self):
        """Test for memory leaks in makeExposure (the test is in utilsTests.MemoryTestCase)"""
        afwImage.makeMaskedImage(afwImage.ImageU(afwGeom.Extent2I(10, 20)))
        afwImage.makeExposure(afwImage.makeMaskedImage(afwImage.ImageU(afwGeom.Extent2I(10, 20))))

    def testImageSlices(self):
        """Test image slicing, which generate sub-images using Box2I under the covers"""
        exp = afwImage.ExposureF(10, 20)
        mi = exp.getMaskedImage()
        mi[9, 19] = 10
        # N.b. Exposures don't support setting/getting the pixels so can't replicate e.g. Image's slice tests
        sexp = exp[1:4, 6:10]
        self.assertEqual(sexp.getDimensions(), afwGeom.ExtentI(3, 4))
        sexp = exp[..., -3:]
        self.assertEqual(sexp.getDimensions(), afwGeom.ExtentI(exp.getWidth(), 3))
        self.assertEqual(sexp.getMaskedImage().get(sexp.getWidth() - 1, sexp.getHeight() - 1),
                          exp.getMaskedImage().get( exp.getWidth() - 1,  exp.getHeight() - 1))

    def testConversionToScalar(self):
        """Test that even 1-pixel Exposures can't be converted to scalars"""
        im = afwImage.ExposureF(10, 20)

        self.assertRaises(TypeError, float, im) # only single pixel images may be converted
        self.assertRaises(TypeError, float, im[0,0]) # actually, can't convert (img, msk, var) to scalar

    def testReadMetadata(self):
        with utilsTests.getTempFilePath(".fits") as tmpFile:
            self.exposureCrWcs.getMetadata().set("FRAZZLE", True)
            # This will write the main metadata (inc. FRAZZLE) to the primary HDU, and the
            # WCS to subsequent HDUs, along with INHERIT=T.
            self.exposureCrWcs.writeFits(tmpFile)
            # This should read the first non-empty HDU (i.e. it skips the primary), but
            # goes back and reads it if it finds INHERIT=T.  That should let us read
            # frazzle and the Wcs from the PropertySet returned by readMetadata.
            md = afwImage.readMetadata(tmpFile)
            wcs = afwImage.makeWcs(md, True)
            self.assertEqual(wcs.getPixelOrigin(), self.wcs.getPixelOrigin())
            self.assertEqual(wcs.getSkyOrigin(), self.wcs.getSkyOrigin())
            self.assert_(numpy.all(wcs.getCDMatrix() == self.wcs.getCDMatrix()))
            frazzle = md.get("FRAZZLE")
            self.assert_(frazzle is True)

    def testArchiveKeys(self):
        with utilsTests.getTempFilePath(".fits") as tmpFile:
            exposure1 = afwImage.ExposureF(100, 100, self.wcs)
            exposure1.setPsf(self.psf)
            exposure1.writeFits(tmpFile)
            exposure2 = afwImage.ExposureF(tmpFile)
            self.assertFalse(exposure2.getMetadata().exists("AR_ID"))
            self.assertFalse(exposure2.getMetadata().exists("PSF_ID"))
            self.assertFalse(exposure2.getMetadata().exists("WCS_ID"))

    def testTicket2861(self):
        with utilsTests.getTempFilePath(".fits") as tmpFile:
            exposure1 = afwImage.ExposureF(100, 100, self.wcs)
            exposure1.setPsf(self.psf)
            schema = afwTable.ExposureTable.makeMinimalSchema()
            coaddInputs = afwImage.CoaddInputs(schema, schema)
            exposure1.getInfo().setCoaddInputs(coaddInputs)
            exposure2 = afwImage.ExposureF(exposure1, True)
            self.assertIsNotNone(exposure2.getInfo().getCoaddInputs())
            exposure2.writeFits(tmpFile)
            exposure3 = afwImage.ExposureF(tmpFile)
            self.assertIsNotNone(exposure3.getInfo().getCoaddInputs())
Example #38
0
    def testTransformAccess(self):
        """Test hasTransform and getTransform
        """
        detector = DetectorWrapper().detector
        for fromSys in (cameraGeom.FOCAL_PLANE, cameraGeom.PIXELS, cameraGeom.TAN_PIXELS):
            fullFromSys = detector.makeCameraSys(fromSys)
            for toSys in (cameraGeom.FOCAL_PLANE, cameraGeom.PIXELS, cameraGeom.TAN_PIXELS):
                fullToSys = detector.makeCameraSys(toSys)
                self.assertTrue(detector.hasTransform(fromSys))
                self.assertTrue(detector.hasTransform(fullFromSys))
                self.assertTrue(detector.hasTransform(toSys))
                self.assertTrue(detector.hasTransform(fullToSys))
                detector.getTransform(fromSys, toSys)
                detector.getTransform(fromSys, fullToSys)
                detector.getTransform(fullFromSys, toSys)
                detector.getTransform(fullFromSys, fullToSys)

        for badCamSys in (
            cameraGeom.CameraSys("badName"),
            cameraGeom.CameraSys("pixels", "badDetectorName")
        ):
            self.assertFalse(detector.hasTransform(badCamSys))
            self.assertTrue(detector.hasTransform(cameraGeom.PIXELS))
            with self.assertRaises(lsst.pex.exceptions.Exception):
                detector.getTransform(cameraGeom.PIXELS, badCamSys)
Example #39
0
class ExposureTestCase(lsst.utils.tests.TestCase):
    """
    A test case for the Exposure Class
    """

    def setUp(self):
        maskedImage = afwImage.MaskedImageF(inFilePathSmall)
        maskedImageMD = readMetadata(inFilePathSmall)

        self.smallExposure = afwImage.ExposureF(inFilePathSmall)
        self.width = maskedImage.getWidth()
        self.height = maskedImage.getHeight()
        self.wcs = afwGeom.makeSkyWcs(maskedImageMD, False)
        self.md = maskedImageMD
        self.psf = DummyPsf(2.0)
        self.detector = DetectorWrapper().detector

        self.exposureBlank = afwImage.ExposureF()
        self.exposureMiOnly = afwImage.makeExposure(maskedImage)
        self.exposureMiWcs = afwImage.makeExposure(maskedImage, self.wcs)
        # n.b. the (100, 100, ...) form
        self.exposureCrWcs = afwImage.ExposureF(100, 100, self.wcs)
        # test with ExtentI(100, 100) too
        self.exposureCrOnly = afwImage.ExposureF(lsst.geom.ExtentI(100, 100))

        afwImage.Filter.reset()
        afwImage.FilterProperty.reset()

        defineFilter("g", 470.0)

    def tearDown(self):
        del self.smallExposure
        del self.wcs
        del self.psf
        del self.detector

        del self.exposureBlank
        del self.exposureMiOnly
        del self.exposureMiWcs
        del self.exposureCrWcs
        del self.exposureCrOnly

    def testGetMaskedImage(self):
        """
        Test to ensure a MaskedImage can be obtained from each
        Exposure. An Exposure is required to have a MaskedImage,
        therefore each of the Exposures should return a MaskedImage.

        MaskedImage class should throw appropriate
        lsst::pex::exceptions::NotFound if the MaskedImage can not be
        obtained.
        """
        maskedImageBlank = self.exposureBlank.getMaskedImage()
        blankWidth = maskedImageBlank.getWidth()
        blankHeight = maskedImageBlank.getHeight()
        if blankWidth != blankHeight != 0:
            self.fail("%s = %s != 0" % (blankWidth, blankHeight))

        maskedImageMiOnly = self.exposureMiOnly.getMaskedImage()
        miOnlyWidth = maskedImageMiOnly.getWidth()
        miOnlyHeight = maskedImageMiOnly.getHeight()
        self.assertAlmostEqual(miOnlyWidth, self.width)
        self.assertAlmostEqual(miOnlyHeight, self.height)

        # NOTE: Unittests for Exposures created from a MaskedImage and
        # a WCS object are incomplete.  No way to test the validity of
        # the WCS being copied/created.

        maskedImageMiWcs = self.exposureMiWcs.getMaskedImage()
        miWcsWidth = maskedImageMiWcs.getWidth()
        miWcsHeight = maskedImageMiWcs.getHeight()
        self.assertAlmostEqual(miWcsWidth, self.width)
        self.assertAlmostEqual(miWcsHeight, self.height)

        maskedImageCrWcs = self.exposureCrWcs.getMaskedImage()
        crWcsWidth = maskedImageCrWcs.getWidth()
        crWcsHeight = maskedImageCrWcs.getHeight()
        if crWcsWidth != crWcsHeight != 0:
            self.fail("%s != %s != 0" % (crWcsWidth, crWcsHeight))

        maskedImageCrOnly = self.exposureCrOnly.getMaskedImage()
        crOnlyWidth = maskedImageCrOnly.getWidth()
        crOnlyHeight = maskedImageCrOnly.getHeight()
        if crOnlyWidth != crOnlyHeight != 0:
            self.fail("%s != %s != 0" % (crOnlyWidth, crOnlyHeight))

        # Check Exposure.getWidth() returns the MaskedImage's width
        self.assertEqual(crOnlyWidth, self.exposureCrOnly.getWidth())
        self.assertEqual(crOnlyHeight, self.exposureCrOnly.getHeight())

    def testProperties(self):
        self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage,
                                     self.exposureMiOnly.getMaskedImage())
        mi2 = afwImage.MaskedImageF(self.exposureMiOnly.getDimensions())
        mi2.image.array[:] = 5.0
        mi2.variance.array[:] = 3.0
        mi2.mask.array[:] = 0x1
        self.exposureMiOnly.maskedImage = mi2
        self.assertMaskedImagesEqual(self.exposureMiOnly.maskedImage, mi2)
        self.assertImagesEqual(self.exposureMiOnly.image,
                               self.exposureMiOnly.maskedImage.image)

        image3 = afwImage.ImageF(self.exposureMiOnly.getDimensions())
        image3.array[:] = 3.0
        self.exposureMiOnly.image = image3
        self.assertImagesEqual(self.exposureMiOnly.image, image3)

        mask3 = afwImage.MaskX(self.exposureMiOnly.getDimensions())
        mask3.array[:] = 0x2
        self.exposureMiOnly.mask = mask3
        self.assertMasksEqual(self.exposureMiOnly.mask, mask3)

        var3 = afwImage.ImageF(self.exposureMiOnly.getDimensions())
        var3.array[:] = 2.0
        self.exposureMiOnly.variance = var3
        self.assertImagesEqual(self.exposureMiOnly.variance, var3)

    def testGetWcs(self):
        """
        Test if a WCS can be obtained from each Exposure created with
        a WCS.

        Test that appropriate exceptions are thrown if a WCS is
        requested from an Exposure that was not created with a WCS.
        Python turns the pex::exceptions in the Exposure and
        MaskedImage classes into IndexErrors.

        The exposureBlank, exposureMiOnly, and exposureCrOnly
        Exposures should throw a lsst::pex::exceptions::NotFound.
        """

        self.assertFalse(self.exposureBlank.getWcs())
        self.assertFalse(self.exposureMiOnly.getWcs())

        # These two should pass
        self.exposureMiWcs.getWcs()
        self.exposureCrWcs.getWcs()

        self.assertFalse(self.exposureCrOnly.getWcs())

    def testExposureInfoConstructor(self):
        """Test the Exposure(maskedImage, exposureInfo) constructor"""
        exposureInfo = afwImage.ExposureInfo()
        exposureInfo.setWcs(self.wcs)
        exposureInfo.setDetector(self.detector)
        gFilter = afwImage.Filter("g")
        exposureInfo.setFilter(gFilter)
        maskedImage = afwImage.MaskedImageF(inFilePathSmall)
        exposure = afwImage.ExposureF(maskedImage, exposureInfo)

        self.assertTrue(exposure.hasWcs())
        self.assertEqual(exposure.getWcs().getPixelOrigin(),
                         self.wcs.getPixelOrigin())
        self.assertEqual(exposure.getDetector().getName(),
                         self.detector.getName())
        self.assertEqual(exposure.getDetector().getSerial(),
                         self.detector.getSerial())
        self.assertEqual(exposure.getFilter(), gFilter)

        self.assertTrue(exposure.getInfo().hasWcs())
        self.assertEqual(exposure.getInfo().getWcs().getPixelOrigin(),
                         self.wcs.getPixelOrigin())
        self.assertEqual(exposure.getInfo().getDetector().getName(),
                         self.detector.getName())
        self.assertEqual(exposure.getInfo().getDetector().getSerial(),
                         self.detector.getSerial())
        self.assertEqual(exposure.getInfo().getFilter(), gFilter)

    def testNullWcs(self):
        """Test that an Exposure constructed with second argument None is usable

        When the exposureInfo constructor was first added, trying to get a WCS
        or other info caused a segfault because the ExposureInfo did not exist.
        """
        maskedImage = self.exposureMiOnly.getMaskedImage()
        exposure = afwImage.ExposureF(maskedImage, None)
        self.assertFalse(exposure.hasWcs())
        self.assertFalse(exposure.hasPsf())

    def testExposureInfoSetNone(self):
        exposureInfo = afwImage.ExposureInfo()
        exposureInfo.setDetector(None)
        exposureInfo.setValidPolygon(None)
        exposureInfo.setPsf(None)
        exposureInfo.setWcs(None)
        exposureInfo.setPhotoCalib(None)
        exposureInfo.setCoaddInputs(None)
        exposureInfo.setVisitInfo(None)
        exposureInfo.setApCorrMap(None)

    def testSetExposureInfo(self):
        exposureInfo = afwImage.ExposureInfo()
        exposureInfo.setWcs(self.wcs)
        exposureInfo.setDetector(self.detector)
        gFilter = afwImage.Filter("g")
        exposureInfo.setFilter(gFilter)
        maskedImage = afwImage.MaskedImageF(inFilePathSmall)
        exposure = afwImage.ExposureF(maskedImage)
        self.assertFalse(exposure.hasWcs())

        exposure.setInfo(exposureInfo)

        self.assertTrue(exposure.hasWcs())
        self.assertEqual(exposure.getWcs().getPixelOrigin(),
                         self.wcs.getPixelOrigin())
        self.assertEqual(exposure.getDetector().getName(),
                         self.detector.getName())
        self.assertEqual(exposure.getDetector().getSerial(),
                         self.detector.getSerial())
        self.assertEqual(exposure.getFilter(), gFilter)

    def testVisitInfoFitsPersistence(self):
        """Test saving an exposure to FITS and reading it back in preserves (some) VisitInfo fields"""
        exposureId = 5
        exposureTime = 12.3
        boresightRotAngle = 45.6 * lsst.geom.degrees
        weather = Weather(1.1, 2.2, 0.3)
        visitInfo = afwImage.VisitInfo(
            exposureId=exposureId,
            exposureTime=exposureTime,
            boresightRotAngle=boresightRotAngle,
            weather=weather,
        )
        photoCalib = afwImage.PhotoCalib(3.4, 5.6)
        exposureInfo = afwImage.ExposureInfo()
        exposureInfo.setVisitInfo(visitInfo)
        exposureInfo.setPhotoCalib(photoCalib)
        exposureInfo.setDetector(self.detector)
        gFilter = afwImage.Filter("g")
        exposureInfo.setFilter(gFilter)
        maskedImage = afwImage.MaskedImageF(inFilePathSmall)
        exposure = afwImage.ExposureF(maskedImage, exposureInfo)
        with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
            exposure.writeFits(tmpFile)
            rtExposure = afwImage.ExposureF(tmpFile)
        rtVisitInfo = rtExposure.getInfo().getVisitInfo()
        self.assertEqual(rtVisitInfo.getWeather(), weather)
        self.assertEqual(rtExposure.getPhotoCalib(), photoCalib)
        self.assertEqual(rtExposure.getFilter(), gFilter)

    def testSetMembers(self):
        """
        Test that the MaskedImage and the WCS of an Exposure can be set.
        """
        exposure = afwImage.ExposureF()

        maskedImage = afwImage.MaskedImageF(inFilePathSmall)
        exposure.setMaskedImage(maskedImage)
        exposure.setWcs(self.wcs)
        exposure.setDetector(self.detector)
        exposure.setFilter(afwImage.Filter("g"))

        self.assertEqual(exposure.getDetector().getName(),
                         self.detector.getName())
        self.assertEqual(exposure.getDetector().getSerial(),
                         self.detector.getSerial())
        self.assertEqual(exposure.getFilter().getName(), "g")
        self.assertEqual(exposure.getWcs(), self.wcs)

        # The PhotoCalib tests are in test_photoCalib.py;
        # here we just check that it's gettable and settable.
        self.assertIsNone(exposure.getPhotoCalib())

        photoCalib = afwImage.PhotoCalib(511.1, 44.4)
        exposure.setPhotoCalib(photoCalib)
        self.assertEqual(exposure.getPhotoCalib(), photoCalib)

        # Psfs next
        self.assertFalse(exposure.hasPsf())
        exposure.setPsf(self.psf)
        self.assertTrue(exposure.hasPsf())

        exposure.setPsf(DummyPsf(1.0))  # we can reset the Psf

        # Test that we can set the MaskedImage and WCS of an Exposure
        # that already has both
        self.exposureMiWcs.setMaskedImage(maskedImage)
        exposure.setWcs(self.wcs)

    def testHasWcs(self):
        """
        Test if an Exposure has a WCS or not.
        """
        self.assertFalse(self.exposureBlank.hasWcs())

        self.assertFalse(self.exposureMiOnly.hasWcs())
        self.assertTrue(self.exposureMiWcs.hasWcs())
        self.assertTrue(self.exposureCrWcs.hasWcs())
        self.assertFalse(self.exposureCrOnly.hasWcs())

    def testGetSubExposure(self):
        """
        Test that a subExposure of the original Exposure can be obtained.

        The MaskedImage class should throw a
        lsst::pex::exceptions::InvalidParameter if the requested
        subRegion is not fully contained within the original
        MaskedImage.

        """
        #
        # This subExposure is valid
        #
        subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
                                  lsst.geom.Extent2I(10, 10))
        subExposure = self.exposureCrWcs.Factory(
            self.exposureCrWcs, subBBox, afwImage.LOCAL)

        self.checkWcs(self.exposureCrWcs, subExposure)

        # this subRegion is not valid and should trigger an exception
        # from the MaskedImage class and should trigger an exception
        # from the WCS class for the MaskedImage 871034p_1_MI.

        subRegion3 = lsst.geom.Box2I(lsst.geom.Point2I(100, 100),
                                     lsst.geom.Extent2I(10, 10))

        def getSubRegion():
            self.exposureCrWcs.Factory(
                self.exposureCrWcs, subRegion3, afwImage.LOCAL)

        self.assertRaises(pexExcept.LengthError, getSubRegion)

        # this subRegion is not valid and should trigger an exception
        # from the MaskedImage class only for the MaskedImage small_MI.
        # small_MI (cols, rows) = (256, 256)

        subRegion4 = lsst.geom.Box2I(lsst.geom.Point2I(250, 250),
                                     lsst.geom.Extent2I(10, 10))

        def getSubRegion():
            self.exposureCrWcs.Factory(
                self.exposureCrWcs, subRegion4, afwImage.LOCAL)

        self.assertRaises(pexExcept.LengthError, getSubRegion)

        # check the sub- and parent- exposures are using the same Wcs
        # transformation
        subBBox = lsst.geom.Box2I(lsst.geom.Point2I(40, 50),
                                  lsst.geom.Extent2I(10, 10))
        subExposure = self.exposureCrWcs.Factory(
            self.exposureCrWcs, subBBox, afwImage.LOCAL)
        parentSkyPos = self.exposureCrWcs.getWcs().pixelToSky(0, 0)

        subExpSkyPos = subExposure.getWcs().pixelToSky(0, 0)

        self.assertSpherePointsAlmostEqual(parentSkyPos, subExpSkyPos, msg="Wcs in sub image has changed")

    def testReadWriteFits(self):
        """Test readFits and writeFits.
        """
        # This should pass without an exception
        mainExposure = afwImage.ExposureF(inFilePathSmall)
        mainExposure.setDetector(self.detector)

        subBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 10),
                                  lsst.geom.Extent2I(40, 50))
        subExposure = mainExposure.Factory(
            mainExposure, subBBox, afwImage.LOCAL)
        self.checkWcs(mainExposure, subExposure)
        det = subExposure.getDetector()
        self.assertTrue(det)

        subExposure = afwImage.ExposureF(
            inFilePathSmall, subBBox, afwImage.LOCAL)

        self.checkWcs(mainExposure, subExposure)

        # This should throw an exception
        def getExposure():
            afwImage.ExposureF(inFilePathSmallImage)

        self.assertRaises(FitsError, getExposure)

        mainExposure.setPsf(self.psf)

        # Make sure we can write without an exception
        photoCalib = afwImage.PhotoCalib(1e-10, 1e-12)
        mainExposure.setPhotoCalib(photoCalib)

        with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
            mainExposure.writeFits(tmpFile)

            readExposure = type(mainExposure)(tmpFile)

            #
            # Check the round-tripping
            #
            self.assertEqual(mainExposure.getFilter().getName(),
                             readExposure.getFilter().getName())

            self.assertEqual(photoCalib, readExposure.getPhotoCalib())

            psf = readExposure.getPsf()
            self.assertIsNotNone(psf)
            self.assertEqual(psf.getValue(), self.psf.getValue())

    def checkWcs(self, parentExposure, subExposure):
        """Compare WCS at corner points of a sub-exposure and its parent exposure
           By using the function indexToPosition, we should be able to convert the indices
           (of the four corners (of the sub-exposure)) to positions and use the wcs
           to get the same sky coordinates for each.
        """
        subMI = subExposure.getMaskedImage()
        subDim = subMI.getDimensions()

        # Note: pixel positions must be computed relative to XY0 when working
        # with WCS
        mainWcs = parentExposure.getWcs()
        subWcs = subExposure.getWcs()

        for xSubInd in (0, subDim.getX()-1):
            for ySubInd in (0, subDim.getY()-1):
                self.assertSpherePointsAlmostEqual(
                    mainWcs.pixelToSky(
                        afwImage.indexToPosition(xSubInd),
                        afwImage.indexToPosition(ySubInd),
                    ),
                    subWcs.pixelToSky(
                        afwImage.indexToPosition(xSubInd),
                        afwImage.indexToPosition(ySubInd),
                    ))

    def cmpExposure(self, e1, e2):
        self.assertEqual(e1.getDetector().getName(),
                         e2.getDetector().getName())
        self.assertEqual(e1.getDetector().getSerial(),
                         e2.getDetector().getSerial())
        self.assertEqual(e1.getFilter().getName(), e2.getFilter().getName())
        xy = lsst.geom.Point2D(0, 0)
        self.assertEqual(e1.getWcs().pixelToSky(xy)[0],
                         e2.getWcs().pixelToSky(xy)[0])
        self.assertEqual(e1.getPhotoCalib(), e2.getPhotoCalib())
        # check PSF identity
        if not e1.getPsf():
            self.assertFalse(e2.getPsf())
        else:
            self.assertEqual(e1.getPsf().getValue(), e2.getPsf().getValue())

    def testCopyExposure(self):
        """Copy an Exposure (maybe changing type)"""

        exposureU = afwImage.ExposureU(inFilePathSmall, allowUnsafe=True)
        exposureU.setWcs(self.wcs)
        exposureU.setDetector(self.detector)
        exposureU.setFilter(afwImage.Filter("g"))
        exposureU.setPsf(DummyPsf(4.0))

        exposureF = exposureU.convertF()
        self.cmpExposure(exposureF, exposureU)

        nexp = exposureF.Factory(exposureF, False)
        self.cmpExposure(exposureF, nexp)

        # Ensure that the copy was deep.
        # (actually this test is invalid since getDetector() returns a shared_ptr)
        # cen0 = exposureU.getDetector().getCenterPixel()
        # x0,y0 = cen0
        # det = exposureF.getDetector()
        # det.setCenterPixel(lsst.geom.Point2D(999.0, 437.8))
        # self.assertEqual(exposureU.getDetector().getCenterPixel()[0], x0)
        # self.assertEqual(exposureU.getDetector().getCenterPixel()[1], y0)

    def testDeepCopyData(self):
        """Make sure a deep copy of an Exposure has its own data (ticket #2625)
        """
        exp = afwImage.ExposureF(6, 7)
        mi = exp.getMaskedImage()
        mi.getImage().set(100)
        mi.getMask().set(5)
        mi.getVariance().set(200)

        expCopy = exp.clone()
        miCopy = expCopy.getMaskedImage()
        miCopy.getImage().set(-50)
        miCopy.getMask().set(2)
        miCopy.getVariance().set(175)

        self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
        self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
        self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)

        self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
        self.assertTrue(np.all(mi.getMask().getArray() == 5))
        self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)

    def testDeepCopySubData(self):
        """Make sure a deep copy of a subregion of an Exposure has its own data (ticket #2625)
        """
        exp = afwImage.ExposureF(6, 7)
        mi = exp.getMaskedImage()
        mi.getImage().set(100)
        mi.getMask().set(5)
        mi.getVariance().set(200)

        bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 4))
        expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
        miCopy = expCopy.getMaskedImage()
        miCopy.getImage().set(-50)
        miCopy.getMask().set(2)
        miCopy.getVariance().set(175)

        self.assertFloatsAlmostEqual(miCopy.getImage().getArray(), -50)
        self.assertTrue(np.all(miCopy.getMask().getArray() == 2))
        self.assertFloatsAlmostEqual(miCopy.getVariance().getArray(), 175)

        self.assertFloatsAlmostEqual(mi.getImage().getArray(), 100)
        self.assertTrue(np.all(mi.getMask().getArray() == 5))
        self.assertFloatsAlmostEqual(mi.getVariance().getArray(), 200)

    def testDeepCopyMetadata(self):
        """Make sure a deep copy of an Exposure has a deep copy of metadata (ticket #2568)
        """
        exp = afwImage.ExposureF(10, 10)
        expMeta = exp.getMetadata()
        expMeta.set("foo", 5)
        expCopy = exp.clone()
        expCopyMeta = expCopy.getMetadata()
        expCopyMeta.set("foo", 6)
        self.assertEqual(expCopyMeta.getScalar("foo"), 6)
        # this will fail if the bug is present
        self.assertEqual(expMeta.getScalar("foo"), 5)

    def testDeepCopySubMetadata(self):
        """Make sure a deep copy of a subregion of an Exposure has a deep copy of metadata (ticket #2568)
        """
        exp = afwImage.ExposureF(10, 10)
        expMeta = exp.getMetadata()
        expMeta.set("foo", 5)
        bbox = lsst.geom.Box2I(lsst.geom.Point2I(1, 0), lsst.geom.Extent2I(5, 5))
        expCopy = exp.Factory(exp, bbox, afwImage.PARENT, True)
        expCopyMeta = expCopy.getMetadata()
        expCopyMeta.set("foo", 6)
        self.assertEqual(expCopyMeta.getScalar("foo"), 6)
        # this will fail if the bug is present
        self.assertEqual(expMeta.getScalar("foo"), 5)

    def testMakeExposureLeaks(self):
        """Test for memory leaks in makeExposure (the test is in lsst.utils.tests.MemoryTestCase)"""
        afwImage.makeMaskedImage(afwImage.ImageU(lsst.geom.Extent2I(10, 20)))
        afwImage.makeExposure(afwImage.makeMaskedImage(
            afwImage.ImageU(lsst.geom.Extent2I(10, 20))))

    def testImageSlices(self):
        """Test image slicing, which generate sub-images using Box2I under the covers"""
        exp = afwImage.ExposureF(10, 20)
        mi = exp.getMaskedImage()
        mi.image[9, 19] = 10
        # N.b. Exposures don't support setting/getting the pixels so can't
        # replicate e.g. Image's slice tests
        sexp = exp[1:4, 6:10]
        self.assertEqual(sexp.getDimensions(), lsst.geom.ExtentI(3, 4))
        sexp = exp[:, -3:, afwImage.LOCAL]
        self.assertEqual(sexp.getDimensions(),
                         lsst.geom.ExtentI(exp.getWidth(), 3))
        self.assertEqual(sexp.maskedImage[-1, -1, afwImage.LOCAL],
                         exp.maskedImage[-1, -1, afwImage.LOCAL])

    def testConversionToScalar(self):
        """Test that even 1-pixel Exposures can't be converted to scalars"""
        im = afwImage.ExposureF(10, 20)

        # only single pixel images may be converted
        self.assertRaises(TypeError, float, im)
        # actually, can't convert (img, msk, var) to scalar
        self.assertRaises(TypeError, float, im[0, 0])

    def testReadMetadata(self):
        with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
            self.exposureCrWcs.getMetadata().set("FRAZZLE", True)
            # This will write the main metadata (inc. FRAZZLE) to the primary HDU, and the
            # WCS to subsequent HDUs, along with INHERIT=T.
            self.exposureCrWcs.writeFits(tmpFile)
            # This should read the first non-empty HDU (i.e. it skips the primary), but
            # goes back and reads it if it finds INHERIT=T.  That should let us read
            # frazzle and the Wcs from the PropertySet returned by
            # testReadMetadata.
            md = readMetadata(tmpFile)
            wcs = afwGeom.makeSkyWcs(md, False)
            self.assertPairsAlmostEqual(wcs.getPixelOrigin(), self.wcs.getPixelOrigin())
            self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), self.wcs.getSkyOrigin())
            assert_allclose(wcs.getCdMatrix(), self.wcs.getCdMatrix(), atol=1e-10)
            frazzle = md.getScalar("FRAZZLE")
            self.assertTrue(frazzle)

    def testArchiveKeys(self):
        with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
            exposure1 = afwImage.ExposureF(100, 100, self.wcs)
            exposure1.setPsf(self.psf)
            exposure1.writeFits(tmpFile)
            exposure2 = afwImage.ExposureF(tmpFile)
            self.assertFalse(exposure2.getMetadata().exists("AR_ID"))
            self.assertFalse(exposure2.getMetadata().exists("PSF_ID"))
            self.assertFalse(exposure2.getMetadata().exists("WCS_ID"))

    def testTicket2861(self):
        with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
            exposure1 = afwImage.ExposureF(100, 100, self.wcs)
            exposure1.setPsf(self.psf)
            schema = afwTable.ExposureTable.makeMinimalSchema()
            coaddInputs = afwImage.CoaddInputs(schema, schema)
            exposure1.getInfo().setCoaddInputs(coaddInputs)
            exposure2 = afwImage.ExposureF(exposure1, True)
            self.assertIsNotNone(exposure2.getInfo().getCoaddInputs())
            exposure2.writeFits(tmpFile)
            exposure3 = afwImage.ExposureF(tmpFile)
            self.assertIsNotNone(exposure3.getInfo().getCoaddInputs())

    def testGetCutout(self):
        wcs = self.smallExposure.getWcs()

        dimensions = [lsst.geom.Extent2I(100, 50), lsst.geom.Extent2I(15, 15), lsst.geom.Extent2I(0, 10),
                      lsst.geom.Extent2I(25, 30), lsst.geom.Extent2I(15, -5),
                      2*self.smallExposure.getDimensions()]
        locations = [("center", self._getExposureCenter(self.smallExposure)),
                     ("edge", wcs.pixelToSky(lsst.geom.Point2D(0, 0))),
                     ("rounding test", wcs.pixelToSky(lsst.geom.Point2D(0.2, 0.7))),
                     ("just inside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 + 1e-4, -0.5 + 1e-4))),
                     ("just outside", wcs.pixelToSky(lsst.geom.Point2D(-0.5 - 1e-4, -0.5 - 1e-4))),
                     ("outside", wcs.pixelToSky(lsst.geom.Point2D(-1000, -1000)))]
        for cutoutSize in dimensions:
            for label, cutoutCenter in locations:
                msg = 'Cutout size = %s, location = %s' % (cutoutSize, label)
                if "outside" not in label and all(cutoutSize.gt(0)):
                    cutout = self.smallExposure.getCutout(cutoutCenter, cutoutSize)
                    centerInPixels = wcs.skyToPixel(cutoutCenter)
                    precision = (1 + 1e-4)*np.sqrt(0.5)*wcs.getPixelScale(centerInPixels)
                    self._checkCutoutProperties(cutout, cutoutSize, cutoutCenter, precision, msg)
                    self._checkCutoutPixels(
                        cutout,
                        self._getValidCorners(self.smallExposure.getBBox(), cutout.getBBox()),
                        msg)

                    # Need a valid WCS
                    with self.assertRaises(pexExcept.LogicError, msg=msg):
                        self.exposureMiOnly.getCutout(cutoutCenter, cutoutSize)
                else:
                    with self.assertRaises(pexExcept.InvalidParameterError, msg=msg):
                        self.smallExposure.getCutout(cutoutCenter, cutoutSize)

    def _checkCutoutProperties(self, cutout, size, center, precision, msg):
        """Test whether a cutout has the desired size and position.

        Parameters
        ----------
        cutout : `lsst.afw.image.Exposure`
            The cutout to test.
        size : `lsst.geom.Extent2I`
            The expected dimensions of ``cutout``.
        center : `lsst.geom.SpherePoint`
            The expected center of ``cutout``.
        precision : `lsst.geom.Angle`
            The precision to which ``center`` must match.
        msg : `str`
            An error message suffix describing test parameters.
        """
        newCenter = self._getExposureCenter(cutout)
        self.assertIsNotNone(cutout, msg=msg)
        self.assertSpherePointsAlmostEqual(newCenter, center, maxSep=precision, msg=msg)
        self.assertEqual(cutout.getWidth(), size[0], msg=msg)
        self.assertEqual(cutout.getHeight(), size[1], msg=msg)

    def _checkCutoutPixels(self, cutout, validCorners, msg):
        """Test whether a cutout has valid/empty pixels where expected.

        Parameters
        ----------
        cutout : `lsst.afw.image.Exposure`
            The cutout to test.
        validCorners : iterable of `lsst.geom.Point2I`
            The corners of ``cutout`` that should be drawn from the original image.
        msg : `str`
            An error message suffix describing test parameters.
        """
        mask = cutout.getMaskedImage().getMask()
        edgeMask = mask.getPlaneBitMask("NO_DATA")

        for corner in cutout.getBBox().getCorners():
            maskBitsSet = mask[corner] & edgeMask
            if corner in validCorners:
                self.assertEqual(maskBitsSet, 0, msg=msg)
            else:
                self.assertEqual(maskBitsSet, edgeMask, msg=msg)

    def _getExposureCenter(self, exposure):
        """Return the sky coordinates of an Exposure's center.

        Parameters
        ----------
        exposure : `lsst.afw.image.Exposure`
            The image whose center is desired.

        Returns
        -------
        center : `lsst.geom.SpherePoint`
            The position at the center of ``exposure``.
        """
        return exposure.getWcs().pixelToSky(lsst.geom.Box2D(exposure.getBBox()).getCenter())

    def _getValidCorners(self, imageBox, cutoutBox):
        """Return the corners of a cutout that are constrained by the original image.

        Parameters
        ----------
        imageBox: `lsst.geom.Extent2I`
            The bounding box of the original image.
        cutoutBox : `lsst.geom.Box2I`
            The bounding box of the cutout.

        Returns
        -------
        corners : iterable of `lsst.geom.Point2I`
            The corners that are drawn from the original image.
        """
        return [corner for corner in cutoutBox.getCorners() if corner in imageBox]
class ApplyLookupTableTestCase(lsst.utils.tests.TestCase):
    """Test IsrTask.addDistortionModel
    """

    def setUp(self):
        self.camera = CameraWrapper().camera
        self.detector = DetectorWrapper().detector
        self.crpix = afwGeom.Point2D(50, 100)
        self.crval = afwGeom.SpherePoint(36, 71, afwGeom.degrees)
        scale = 1.0*afwGeom.arcseconds
        self.cdMatrix = afwGeom.makeCdMatrix(scale=scale)
        self.wcs = afwGeom.makeSkyWcs(crpix=self.crpix, crval=self.crval, cdMatrix=self.cdMatrix)
        self.bbox = afwGeom.Box2I(afwGeom.Point2I(-10, 10), afwGeom.Extent2I(1000, 1022))
        self.exposure = ExposureF(self.bbox)

        # set the few items of ExposureInfo needed by IsrTask.run
        # when only adding a distortion model
        exposureInfo = ExposureInfo(photoCalib=PhotoCalib(1.0),
                                    detector=self.detector,
                                    visitInfo=VisitInfo(exposureTime=1.0),
                                    wcs=self.wcs)

        self.exposure.setInfo(exposureInfo)

    def tearDown(self):
        self.detector = None
        self.exposure = None

    def testAddDistortionMethod(self):
        """Call IsrTask.addDistortionModel directly"""
        isrFunctions.addDistortionModel(self.exposure, self.camera)
        self.assertFalse(wcsAlmostEqualOverBBox(self.wcs, self.exposure.getWcs(), self.bbox))

        desiredWcs = self.makeDesiredDistortedWcs()
        self.assertWcsAlmostEqualOverBBox(desiredWcs, self.exposure.getWcs(), self.bbox)

    def makeMinimalIsrConfig(self):
        """Return an IsrConfig with all boolean flags disabled"""
        isrConfig = IsrTask.ConfigClass()
        for name in isrConfig:
            if name.startswith("do"):
                setattr(isrConfig, name, False)
        return isrConfig

    def makeDesiredDistortedWcs(self):
        """Make the expected distorted WCS"""
        pixelToFocalPlane = self.detector.getTransform(PIXELS, FOCAL_PLANE)
        focalPlaneToFieldAngle = self.camera.getTransformMap().getTransform(FOCAL_PLANE, FIELD_ANGLE)
        return makeDistortedTanWcs(self.wcs, pixelToFocalPlane, focalPlaneToFieldAngle)

    def testRunWithAddDistortionModel(self):
        """Test IsrTask.run with config.doAddDistortionModel true"""
        isrConfig = self.makeMinimalIsrConfig()
        isrConfig.doAddDistortionModel = True
        isrTask = IsrTask(config=isrConfig)
        with self.assertRaises(RuntimeError):
            # the camera argument is required
            isrTask.run(ccdExposure=self.exposure)
        exposure = isrTask.run(ccdExposure=self.exposure, camera=self.camera).exposure
        desiredWcs = self.makeDesiredDistortedWcs()
        self.assertWcsAlmostEqualOverBBox(desiredWcs, exposure.getWcs(), self.bbox)

    def testRunWithoutAddDistortionModel(self):
        """Test IsrTask.run with config.doAddDistortionModel false"""
        isrConfig = self.makeMinimalIsrConfig()
        isrTask = IsrTask(config=isrConfig)

        # the camera argument is not needed
        exposure = isrTask.run(ccdExposure=self.exposure).exposure
        self.assertEqual(self.wcs, exposure.getWcs())

        # and the camera argument is ignored if provided
        exposure2 = isrTask.run(ccdExposure=self.exposure, camera=self.camera).exposure
        self.assertEqual(self.wcs, exposure2.getWcs())