def testCurvedFocalPlane(self): """Test a curved focal plane (with rectangular pixels) """ bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1000, 1000)) pixelSizeMm = afwGeom.Extent2D(0.02, 0.03) plateScale = 25.0 # arcsec/mm yaw = afwGeom.Angle(20, afwGeom.degrees) # focal-plane position of ref position on detector (mm) fpPosition = afwGeom.Point2D(50, 25) # ref position on detector (pos of lower left corner) refPoint = afwGeom.Point2D(-0.5, -0.5) orientation = cameraGeom.Orientation( fpPosition, refPoint, yaw, ) pixelToFocalPlane = orientation.makePixelFpTransform(pixelSizeMm) plateScaleRad = afwGeom.Angle(plateScale, afwGeom.arcseconds).asRadians() focalPlaneToField = afwGeom.makeRadialTransform( (0.0, plateScaleRad, 0.0, 0.001 * plateScaleRad)) pixelToField = pixelToFocalPlane.then(focalPlaneToField) pixelToTanPixel = makePixelToTanPixel( bbox=bbox, orientation=orientation, focalPlaneToField=focalPlaneToField, pixelSizeMm=pixelSizeMm, ) # the center point of the field angle frame should not move pixAtFieldCtr = pixelToField.applyInverse(afwGeom.Point2D(0, 0)) tanPixAtFieldCr = pixelToTanPixel.applyForward(pixAtFieldCtr) self.assertPairsAlmostEqual(pixAtFieldCtr, tanPixAtFieldCr) # build same camera geometry transforms without optical distortion focalPlaneToFieldNoDistortion = afwGeom.makeRadialTransform( (0.0, plateScaleRad)) pixelToFieldNoDistortion = pixelToFocalPlane.then( focalPlaneToFieldNoDistortion) for x in (100, 200, 1000): for y in (100, 500, 800): pixPos = afwGeom.Point2D(x, y) tanPixPos = pixelToTanPixel.applyForward(pixPos) # for a given field angle (which, together with a pointing, gives a position on the sky): # - field angle to pixels gives pixPos # - undistorted field angle to pixels gives tanPixPos fieldPos = pixelToField.applyForward(pixPos) desTanPixPos = pixelToFieldNoDistortion.applyInverse(fieldPos) # use a degraded accuracy because small Jacobian errors accumulate this far from the center self.assertPairsAlmostEqual(desTanPixPos, tanPixPos, maxDiff=1e-5)
def testWarnings(self): """Test that approximateWcs raises a UserWarning when it cannot achieve desired tolerance""" radialTransform = afwGeom.makeRadialTransform([0, 2.0, 3.0]) wcs = afwGeom.makeModifiedWcs(pixelTransform=radialTransform, wcs=self.tanWcs, modifyActualPixels=False) with self.assertRaises(UserWarning): approximateWcs(wcs=wcs, bbox=self.bbox, order=2)
def testDistortion(self): """Test computePixelToDistortedPixel with distortion pixelToDistortedPixel -> self.tanWcs should match a WCS created with makeDistortedTanWcs """ focalPlaneToFieldAngle = afwGeom.makeRadialTransform([0.0, self.radPerMm, 0.0, self.radPerMm]) pixelToDistortedPixel = computePixelToDistortedPixel( pixelToFocalPlane=self.pixelToFocalPlane, focalPlaneToFieldAngle=focalPlaneToFieldAngle, ) # Do not try to make pixelToDistortedPixel -> self.tanWcs into a WCS # because the frame names will be wrong; use a TransformPoint2Tolsst.geom.SpherePoint instead tanWcsTransform = afwGeom.TransformPoint2ToSpherePoint(self.tanWcs.getFrameDict()) pixelToDistortedSky = pixelToDistortedPixel.then(tanWcsTransform) wcs = makeDistortedTanWcs( tanWcs=self.tanWcs, pixelToFocalPlane=self.pixelToFocalPlane, focalPlaneToFieldAngle=focalPlaneToFieldAngle, ) bboxD = lsst.geom.Box2D(self.bbox) pixelPoints = bboxD.getCorners() pixelPoints.append(bboxD.getCenter()) skyPoints1 = pixelToDistortedSky.applyForward(pixelPoints) skyPoints2 = wcs.pixelToSky(pixelPoints) self.assertSpherePointListsAlmostEqual(skyPoints1, skyPoints2) pixelPoints1 = pixelToDistortedSky.applyInverse(skyPoints1) pixelPoints2 = wcs.skyToPixel(skyPoints1) assert_allclose(pixelPoints1, pixelPoints2)
def testRadial(self): """Add a radial transform""" for order in (4, 5, 6): self.doTest("testRadial", afwGeom.makeRadialTransform([0, 1.001, 0.000003]), order=order, doPlot=False)
def testDistortion(self): """Test computePixelToDistortedPixel with distortion pixelToDistortedPixel -> self.tanWcs should match a WCS created with makeDistortedTanWcs """ focalPlaneToFieldAngle = afwGeom.makeRadialTransform( [0.0, self.radPerMm, 0.0, self.radPerMm]) pixelToDistortedPixel = computePixelToDistortedPixel( pixelToFocalPlane=self.pixelToFocalPlane, focalPlaneToFieldAngle=focalPlaneToFieldAngle, ) # Do not try to make pixelToDistortedPixel -> self.tanWcs into a WCS # because the frame names will be wrong; use a TransformPoint2Tolsst.geom.SpherePoint instead tanWcsTransform = afwGeom.TransformPoint2ToSpherePoint( self.tanWcs.getFrameDict()) pixelToDistortedSky = pixelToDistortedPixel.then(tanWcsTransform) wcs = makeDistortedTanWcs( tanWcs=self.tanWcs, pixelToFocalPlane=self.pixelToFocalPlane, focalPlaneToFieldAngle=focalPlaneToFieldAngle, ) bboxD = lsst.geom.Box2D(self.bbox) pixelPoints = bboxD.getCorners() pixelPoints.append(bboxD.getCenter()) skyPoints1 = pixelToDistortedSky.applyForward(pixelPoints) skyPoints2 = wcs.pixelToSky(pixelPoints) self.assertSpherePointListsAlmostEqual(skyPoints1, skyPoints2) pixelPoints1 = pixelToDistortedSky.applyInverse(skyPoints1) pixelPoints2 = wcs.skyToPixel(skyPoints1) assert_allclose(pixelPoints1, pixelPoints2)
def makeCameraGeom(self): """Make a camera geometry. Returns ------- cameraGeom : `lsst.afw.cameraGeom.Camera` Camera geometry. Notes ----- There is one field per entry in self.detectorFracPosList with specifications set by self.detectorWidthPix, self.detectorHeightPix, and self.pixelSizeMm. The plate scale is set by self.plateScale and the amount of optical distortion is fixed. All detectors have the same shape (unlike LSST) and orientation (unlike HSC). Varying these is not necessary for testing the CBP and having all detectors the same simplifies the code. """ radialCoeff = np.array([0.0, 1.0, 0.0, 0.925 ]) / self.plateScale.asRadians() fieldAngleToFocalPlane = makeRadialTransform(radialCoeff) focalPlaneToFieldAngle = fieldAngleToFocalPlane.inverted() cameraTransformMap = cameraGeom.TransformMap( cameraGeom.FOCAL_PLANE, {cameraGeom.FIELD_ANGLE: focalPlaneToFieldAngle}) detectorList = self._makeDetectorList(focalPlaneToFieldAngle) return cameraGeom.Camera("test", detectorList, cameraTransformMap)
def testLinearize(self): for transform, invertible in ( (afwGeom.TransformPoint2ToPoint2(makeForwardPolyMap(2, 2)), False), (afwGeom.makeIdentityTransform(), True), (afwGeom.makeTransform(lsst.geom.AffineTransform(np.array([[3.0, -2.0], [2.0, -1.0]]))), True), (afwGeom.makeRadialTransform([0.0, 8.0e-05, 0.0, -4.5e-12]), True), ): self.checkLinearize(transform, invertible)
def testBadRadial(self): """Test radial with invalid coefficients """ for badCoeffs in ( (0.0,), # len(coeffs) must be > 1 (0.1, 1.0), # coeffs[0] must be zero (0.0, 0.0), # coeffs[1] must be nonzero (0.0, 0.0, 0.1), # coeffs[1] must be nonzero ): with self.assertRaises(pexExcept.InvalidParameterError): afwGeom.makeRadialTransform(badCoeffs) radialFactory = afwGeom.transformRegistry["radial"] radialConfig = radialFactory.ConfigClass() radialConfig.coeffs = badCoeffs with self.assertRaises(Exception): radialConfig.validate()
def testRadial(self): """Add radial distortion""" radialTransform = afwGeom.makeRadialTransform([0, 1.02, 1e-6]) def radialDistortion(x, y): x, y = radialTransform.applyForward(lsst.geom.Point2D(x, y)) return (x, y) self.doTest("testRadial", radialDistortion)
def testBadRadial(self): """Test radial with invalid coefficients """ for badCoeffs in ( (0.0, ), # len(coeffs) must be > 1 (0.1, 1.0), # coeffs[0] must be zero (0.0, 0.0), # coeffs[1] must be nonzero (0.0, 0.0, 0.1), # coeffs[1] must be nonzero ): with self.assertRaises(pexExcept.InvalidParameterError): afwGeom.makeRadialTransform(badCoeffs) radialFactory = afwGeom.transformRegistry["radial"] radialConfig = radialFactory.ConfigClass() radialConfig.coeffs = badCoeffs with self.assertRaises(Exception): radialConfig.validate()
def makeTransformDict(nativeSys, transformDict, plateScale): """Make a dictionary of TransformPoint2ToPoint2s from yaml, mapping from nativeSys Parameters ---------- nativeSys : `lsst.afw.cameraGeom.CameraSys` transformDict : `dict` A dict specifying parameters of transforms; keys are camera system names. plateScale : `lsst.geom.Angle` The size of a pixel in angular units/mm (e.g. 20 arcsec/mm for LSST) Returns ------- transforms : `dict` A dict of `lsst.afw.cameraGeom.CameraSys` : `lsst.afw.geom.TransformPoint2ToPoint2` The resulting dict's keys are `~lsst.afw.cameraGeom.CameraSys`, and the values are Transforms *from* NativeSys *to* CameraSys """ # As other comments note this is required, and this is one function where # it's assumed assert nativeSys == cameraGeom.FOCAL_PLANE, "Cameras with nativeSys != FOCAL_PLANE are not supported." resMap = dict() for key, transform in transformDict.items(): transformType = transform["transformType"] knownTransformTypes = ["affine", "radial"] if transformType not in knownTransformTypes: raise RuntimeError("Saw unknown transform type for %s: %s (known types are: [%s])" % ( key, transform["transformType"], ", ".join(knownTransformTypes))) if transformType == "affine": affine = geom.AffineTransform(np.array(transform["linear"]), np.array(transform["translation"])) transform = afwGeom.makeTransform(affine) elif transformType == "radial": # radial coefficients of the form [0, 1 (no units), C2 (rad), # usually 0, C3 (rad^2), ...] # Radial distortion is modeled as a radial polynomial that converts # from focal plane radius (in mm) to field angle (in radians). # The provided coefficients are divided by the plate # scale (in radians/mm) meaning that C1 is always 1. radialCoeffs = np.array(transform["coeffs"]) radialCoeffs *= plateScale.asRadians() transform = afwGeom.makeRadialTransform(radialCoeffs) else: raise RuntimeError("Impossible condition \"%s\" is not in: [%s])" % ( transform["transformType"], ", ".join(knownTransformTypes))) resMap[cameraGeom.CameraSys(key)] = transform return resMap
def testRadial(self): """Add radial distortion""" radialTransform = afwGeom.makeRadialTransform([0, 1.02, 1e-6]) def radialDistortion(x, y): x, y = radialTransform.applyForward(afwGeom.Point2D(x, y)) return (x, y) self.doTest("testRadial", radialDistortion)
def _makeRadialTransform(self, radialCoeffs): """Helper function to get the radial transform given the radial polynomial coefficients given in the constructor. @param[in] radialCoeffs List of coefficients describing a polynomial radial distortion in normalized units. @return Transform object describing the radial distortion """ pScaleRad = afwGeom.arcsecToRad(self.plateScale) return afwGeom.makeRadialTransform( [el / pScaleRad for el in radialCoeffs])
def testRadial(self): """Add radial distortion""" radialTransform = afwGeom.makeRadialTransform([0, 1.01, 1e-8]) def radialDistortion(x, y): x, y = radialTransform.applyForward(afwGeom.Point2D(x, y)) return (x, y) for order in (4, 5, 6): doPrint = order == 5 self.doTest("testRadial", radialDistortion, order=order, doPrint=doPrint)
def testRadial(self): """Add radial distortion""" radialTransform = afwGeom.makeRadialTransform([0, 1.01, 1e-8]) def radialDistortion(x, y): x, y = radialTransform.applyForward(lsst.geom.Point2D(x, y)) return (x, y) for order in (4, 5, 6): doPrint = order == 5 self.doTest("testRadial", radialDistortion, order=order, doPrint=doPrint)
def testDistortion(self): """Test makeDistortedTanWcs using a non-affine transform for pixelToFocalPlane """ # Compute a distorted wcs that matches self.tanWcs at the center of the field; # the amount of distortion is 10s of pixels over the detector fieldAngleToFocalPlane = afwGeom.makeRadialTransform( [0.0, 1 / self.radPerMm, 0.0, 1000 / self.radPerMm]) focalPlaneToFieldAngle = fieldAngleToFocalPlane.inverted() focalPlaneToTanFieldAngle = self.makeAffineTransform( scale=self.radPerMm) wcs = makeDistortedTanWcs( tanWcs=self.tanWcs, pixelToFocalPlane=self.pixelToFocalPlane, focalPlaneToFieldAngle=focalPlaneToFieldAngle, ) # At the center of the focal plane both WCS should give the same sky position pixelAtCtr = self.pixelToFocalPlane.applyInverse( lsst.geom.Point2D(0, 0)) tanSkyAtCtr = self.tanWcs.pixelToSky(pixelAtCtr) skyAtCtr = wcs.pixelToSky(pixelAtCtr) self.assertPairsAlmostEqual(tanSkyAtCtr, skyAtCtr) # At all reasonable sky points the following field angles should be almost equal: # sky -> tanWcs.skyToPixel -> pixelToFocalPlane -> focalPlaneToTanFieldAngle # sky -> wcs.skyToPixel -> pixelToFocalPlane -> focalPlaneToFieldAngle # where focalPlaneToTanFieldAngle is the linear approximation to # focalPlaneToFieldAngle at the center of the field (where tanWcs and wcs match), # since for a given pointing, field angle gives position on the sky skyPoints = self.tanWcs.pixelToSky(self.pixelPoints) tanFieldAnglePoints = focalPlaneToTanFieldAngle.applyForward( self.pixelToFocalPlane.applyForward( self.tanWcs.skyToPixel(skyPoints))) fieldAnglePoints = focalPlaneToFieldAngle.applyForward( self.pixelToFocalPlane.applyForward(wcs.skyToPixel(skyPoints))) assert_allclose(tanFieldAnglePoints, fieldAnglePoints) # The inverse should also be true: for a set of field angle points # the following sky positions should be almost equal: # fieldAngle -> fieldAngleToTanFocalPlane -> focalPlaneToPixel -> tanWcs.pixelToSky # fieldAngle -> fieldAngleToFocalPlane -> focalPlaneToPixel -> wcs.pixelToSky focalPlaneToPixel = self.pixelToFocalPlane.inverted() fieldAngleToTanFocalPlane = focalPlaneToTanFieldAngle.inverted() tanSkyPoints2 = self.tanWcs.pixelToSky( focalPlaneToPixel.applyForward( fieldAngleToTanFocalPlane.applyForward(fieldAnglePoints))) skyPoints2 = wcs.pixelToSky( focalPlaneToPixel.applyForward( fieldAngleToFocalPlane.applyForward(fieldAnglePoints))) self.assertSpherePointListsAlmostEqual(tanSkyPoints2, skyPoints2)
def testDistortion(self): """Test makeDistortedTanWcs using a non-affine transform for pixelToFocalPlane """ # Compute a distorted wcs that matches self.tanWcs at the center of the field; # the amount of distortion is 10s of pixels over the detector fieldAngleToFocalPlane = afwGeom.makeRadialTransform([0.0, 1/self.radPerMm, 0.0, 1000/self.radPerMm]) focalPlaneToFieldAngle = fieldAngleToFocalPlane.inverted() focalPlaneToTanFieldAngle = self.makeAffineTransform(scale=self.radPerMm) wcs = makeDistortedTanWcs( tanWcs=self.tanWcs, pixelToFocalPlane=self.pixelToFocalPlane, focalPlaneToFieldAngle=focalPlaneToFieldAngle, ) # At the center of the focal plane both WCS should give the same sky position pixelAtCtr = self.pixelToFocalPlane.applyInverse(lsst.geom.Point2D(0, 0)) tanSkyAtCtr = self.tanWcs.pixelToSky(pixelAtCtr) skyAtCtr = wcs.pixelToSky(pixelAtCtr) self.assertPairsAlmostEqual(tanSkyAtCtr, skyAtCtr) # At all reasonable sky points the following field angles should be almost equal: # sky -> tanWcs.skyToPixel -> pixelToFocalPlane -> focalPlaneToTanFieldAngle # sky -> wcs.skyToPixel -> pixelToFocalPlane -> focalPlaneToFieldAngle # where focalPlaneToTanFieldAngle is the linear approximation to # focalPlaneToFieldAngle at the center of the field (where tanWcs and wcs match), # since for a given pointing, field angle gives position on the sky skyPoints = self.tanWcs.pixelToSky(self.pixelPoints) tanFieldAnglePoints = focalPlaneToTanFieldAngle.applyForward( self.pixelToFocalPlane.applyForward(self.tanWcs.skyToPixel(skyPoints))) fieldAnglePoints = focalPlaneToFieldAngle.applyForward( self.pixelToFocalPlane.applyForward(wcs.skyToPixel(skyPoints))) assert_allclose(tanFieldAnglePoints, fieldAnglePoints) # The inverse should also be true: for a set of field angle points # the following sky positions should be almost equal: # fieldAngle -> fieldAngleToTanFocalPlane -> focalPlaneToPixel -> tanWcs.pixelToSky # fieldAngle -> fieldAngleToFocalPlane -> focalPlaneToPixel -> wcs.pixelToSky focalPlaneToPixel = self.pixelToFocalPlane.inverted() fieldAngleToTanFocalPlane = focalPlaneToTanFieldAngle.inverted() tanSkyPoints2 = self.tanWcs.pixelToSky( focalPlaneToPixel.applyForward( fieldAngleToTanFocalPlane.applyForward(fieldAnglePoints))) skyPoints2 = wcs.pixelToSky( focalPlaneToPixel.applyForward( fieldAngleToFocalPlane.applyForward(fieldAnglePoints))) self.assertSpherePointListsAlmostEqual(tanSkyPoints2, skyPoints2)
def __init__(self): plateScale = afwGeom.Angle(20, afwGeom.arcseconds) # plate scale, in angle on sky/mm # Radial distortion is modeled as a radial polynomial that converts from focal plane (in mm) # to field angle (in radians). Thus the coefficients are: # C0: always 0, for continuity at the center of the focal plane; units are rad # C1: 1/plateScale; units are rad/mm # C2: usually 0; units are rad/mm^2 # C3: radial distortion; units are rad/mm^3 radialCoeff = np.array([0.0, 1.0, 0.0, 0.925]) / plateScale.asRadians() fieldAngleToFocalPlane = afwGeom.makeRadialTransform(radialCoeff) focalPlaneToFieldAngle = fieldAngleToFocalPlane.inverted() cameraTransformMap = cameraGeom.TransformMap(cameraGeom.FOCAL_PLANE, {cameraGeom.FIELD_ANGLE: focalPlaneToFieldAngle}) detectorList = self._makeDetectorList(focalPlaneToFieldAngle) cameraGeom.Camera.__init__(self, "test", detectorList, cameraTransformMap)
def testLargeDistortion(self): # This transform is about as extreme as I can get: # using 0.0005 in the last value appears to produce numerical issues. # It produces a maximum deviation of 459 pixels, which should be sufficient. pixelsToTanPixels = afwGeom.makeRadialTransform([0.0, 1.1, 0.0004]) self.distortedWcs = afwGeom.makeModifiedWcs(pixelTransform=pixelsToTanPixels, wcs=self.wcs, modifyActualPixels=False) def applyDistortion(src): out = src.table.copyRecord(src) out.set(out.table.getCentroidKey(), pixelsToTanPixels.applyInverse(src.getCentroid())) return out self.singleTestInstance(self.filename, applyDistortion)
def __new__(cls): plateScale = geom.Angle(20, geom.arcseconds) # plate scale, in angle on sky/mm # Radial distortion is modeled as a radial polynomial that converts from focal plane (in mm) # to field angle (in radians). Thus the coefficients are: # C0: always 0, for continuity at the center of the focal plane; units are rad # C1: 1/plateScale; units are rad/mm # C2: usually 0; units are rad/mm^2 # C3: radial distortion; units are rad/mm^3 radialCoeff = np.array([0.0, 1.0, 0.0, 0.925]) / plateScale.asRadians() fieldAngleToFocalPlane = afwGeom.makeRadialTransform(radialCoeff) focalPlaneToFieldAngle = fieldAngleToFocalPlane.inverted() camera = cameraGeom.Camera.Builder("test") cls._makeDetectors(camera, focalPlaneToFieldAngle) camera.setTransformFromFocalPlaneTo(cameraGeom.FIELD_ANGLE, focalPlaneToFieldAngle) return camera.finish()
def __init__(self): """Construct a TestCamera """ plateScale = afwGeom.Angle( 13.55, afwGeom.arcseconds) # plate scale, in angle on sky/mm radialDistortion = 0. # radial distortion in mm/rad^2 radialCoeff = numpy.array( (0.0, 1.0, 0.0, radialDistortion)) / plateScale.asRadians() focalPlaneToFieldAngle = afwGeom.makeRadialTransform(radialCoeff) fieldAngleToFocalPlane = focalPlaneToFieldAngle.inverted() cameraTransformMap = cameraGeom.TransformMap( cameraGeom.FOCAL_PLANE, {cameraGeom.FIELD_ANGLE: fieldAngleToFocalPlane}) detectorList = self._makeDetectorList(fieldAngleToFocalPlane, plateScale) cameraGeom.Camera.__init__(self, "monocam", detectorList, cameraTransformMap)
def testRadial(self): """Test radial = radial Transform """ radialFactory = afwGeom.transformRegistry["radial"] radialConfig = radialFactory.ConfigClass() radialConfig.coeffs = (0.0, 8.5165e-05, 0.0, -4.5014e-12) def check(transform): self.checkRadial(transform, radialConfig.coeffs) self.checkGenericTransform(radialFactory, radialConfig, check) invertibleCoeffs = (0.0, 1.0, 0.05) inverseCoeffs = (0.0, 1.0, -0.05, 0.005, -0.000625, 0.0000875, -1.3125e-5, 2.0625e-6, -3.3515625e-7, 5.5859375e-8, -9.49609375e-9, 1.640234375e-9, -2.870410156e-10) transform = afwGeom.makeRadialTransform(invertibleCoeffs, inverseCoeffs) self.checkRadialInvertible(transform, invertibleCoeffs)
def testMakeModifiedWcsNoActualPixels(self): """Test makeModifiedWcs on a SkyWcs that has no ACTUAL_PIXELS frame """ cdMatrix = makeCdMatrix(scale=self.scale) originalWcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix) originalFrameDict = originalWcs.getFrameDict() # make an arbitrary but reasonable transform to insert using makeModifiedWcs pixelTransform = makeRadialTransform([0.0, 1.0, 0.0, 0.0011]) # the result of the insertion should be as follows desiredPixelsToSky = pixelTransform.then(originalWcs.getTransform()) pixPointList = ( # arbitrary but reasonable Point2D(0.0, 0.0), Point2D(1000.0, 0.0), Point2D(0.0, 2000.0), Point2D(-1111.0, -2222.0), ) for modifyActualPixels in (False, True): modifiedWcs = makeModifiedWcs( pixelTransform=pixelTransform, wcs=originalWcs, modifyActualPixels=modifyActualPixels) modifiedFrameDict = modifiedWcs.getFrameDict() skyList = modifiedWcs.pixelToSky(pixPointList) # compare pixels to sky desiredSkyList = desiredPixelsToSky.applyForward(pixPointList) self.assertSpherePointListsAlmostEqual(skyList, desiredSkyList) # compare pixels to IWC pixelsToIwc = TransformPoint2ToPoint2( modifiedFrameDict.getMapping("PIXELS", "IWC")) desiredPixelsToIwc = TransformPoint2ToPoint2( pixelTransform.getMapping().then( originalFrameDict.getMapping("PIXELS", "IWC"))) self.assertPairListsAlmostEqual( pixelsToIwc.applyForward(pixPointList), desiredPixelsToIwc.applyForward(pixPointList)) self.checkNonFitsWcs(modifiedWcs)
def __init__(self): """Construct a TestCamera """ plateScale = afwGeom.Angle( 20, afwGeom.arcseconds) # plate scale, in angle on sky/mm # Radial distortion is modeled as a radial polynomial that converts from focal plane (in mm) # to field angle (in radians). Thus the coefficients are: # C0: always 0, for continuity at the center of the focal plane; units are rad # C1: 1/plateScale; units are rad/mm # C2: usually 0; units are rad/mm^2 # C3: radial distortion; units are rad/mm^3 radialCoeff = np.array([0.0, 1.0, 0.0, 0.925]) / plateScale.asRadians() fieldAngleToFocalPlane = afwGeom.makeRadialTransform(radialCoeff) focalPlaneToFieldAngle = fieldAngleToFocalPlane.getInverse() cameraTransformMap = cameraGeom.TransformMap( cameraGeom.FOCAL_PLANE, {cameraGeom.FIELD_ANGLE: focalPlaneToFieldAngle}) detectorList = self._makeDetectorList(focalPlaneToFieldAngle) cameraGeom.Camera.__init__(self, "test", detectorList, cameraTransformMap)
def __init__(self, cameraYamlFile): with open(cameraYamlFile) as fd: cameraParams = yaml.load(fd, Loader=yaml.Loader) plateScale = afwGeom.Angle(cameraParams["plateScale"], afwGeom.arcseconds) # radial coefficients of the form [0, no units, 1/rad but usually 0, 1/rad^2, ...] # Radial distortion is modeled as a radial polynomial that converts from focal plane radius (in mm) # to field angle (in radians). The coefficients are divided by the plate scale (in mm/radians) # meaning that C1 is always 1. radialCoeffs = np.array( cameraParams["radialCoeffs"]) / plateScale.asRadians() fieldAngleToFocalPlane = afwGeom.makeRadialTransform(radialCoeffs) focalPlaneToFieldAngle = fieldAngleToFocalPlane.getInverse() cameraTransformMap = cameraGeom.TransformMap( cameraGeom.FOCAL_PLANE, {cameraGeom.FIELD_ANGLE: focalPlaneToFieldAngle}) detectorList = self._makeDetectorList(cameraParams["CCDs"], focalPlaneToFieldAngle) cameraGeom.Camera.__init__(self, cameraParams["name"], detectorList, cameraTransformMap)
def makeCameraGeom(self): """Make a camera geometry. Returns ------- cameraGeom : `lsst.afw.cameraGeom.Camera` Camera geometry. Notes ----- There is one field per entry in self.detectorFracPosList with specifications set by self.detectorWidthPix, self.detectorHeightPix, and self.pixelSizeMm. The plate scale is set by self.plateScale and the amount of optical distortion is fixed. All detectors have the same shape (unlike LSST) and orientation (unlike HSC). Varying these is not necessary for testing the CBP and having all detectors the same simplifies the code. """ radialCoeff = np.array([0.0, 1.0, 0.0, 0.925 ]) / self.plateScale.asRadians() fieldAngleToFocalPlane = makeRadialTransform(radialCoeff) focalPlaneToFieldAngle = fieldAngleToFocalPlane.inverted() cameraBuilder = Camera.Builder("testCamera") cameraBuilder.setTransformFromFocalPlaneTo(FIELD_ANGLE, focalPlaneToFieldAngle) ampBuilder = self._makeAmpBuilder() for i, fpPos in enumerate(self.detectorFracPosList): detectorConfig = self._makeDetectorConfig(id=i, fpPos=fpPos) addDetectorBuilderFromConfig(cameraBuilder, detectorConfig, [ampBuilder], focalPlaneToFieldAngle) return cameraBuilder.finish()
def testFlatFocalPlane(self): """Test an undistorted focal plane (with rectangular pixels) """ bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1000, 1000)) pixelSizeMm = afwGeom.Extent2D(0.02, 0.03) plateScale = 25.0 # arcsec/mm yaw = afwGeom.Angle(20, afwGeom.degrees) # focal-plane position of ref position on detector (mm) fpPosition = afwGeom.Point2D(50, 25) # ref position on detector (pos of lower left corner) refPoint = afwGeom.Point2D(-0.5, -0.5) orientation = cameraGeom.Orientation( fpPosition, refPoint, yaw, ) plateScaleRad = afwGeom.Angle(plateScale, afwGeom.arcseconds).asRadians() focalPlaneToField = afwGeom.makeRadialTransform((0.0, plateScaleRad)) pixelToTanPixel = makePixelToTanPixel( bbox=bbox, orientation=orientation, focalPlaneToField=focalPlaneToField, pixelSizeMm=pixelSizeMm, ) # with no distortion, this should be a unity transform for pointPix in ( afwGeom.Point2D(0, 0), afwGeom.Point2D(1000, 2000), afwGeom.Point2D(-100.5, 27.23), ): pointTanPix = pixelToTanPixel.applyForward(pointPix) self.assertPairsAlmostEqual(pointTanPix, pointPix)
def testRadial(self): """Test fit with radial distortion The offset comes from the fact that the CCD is not centered """ self.doTest(afwGeom.makeRadialTransform([0, 1.01, 1e-7]))
def setUp(self): self.nativeSys = cameraGeom.FOCAL_PLANE self.fieldTransform = afwGeom.makeRadialTransform([0, 0.5, 0.005]) transforms = {cameraGeom.FIELD_ANGLE: self.fieldTransform} self.transformMap = cameraGeom.TransformMap(self.nativeSys, transforms)
def setUp(self): self.nativeSys = cameraGeom.FOCAL_PLANE self.fieldTransform = afwGeom.makeRadialTransform([0, 0.5, 0.005]) transforms = {cameraGeom.FIELD_ANGLE: self.fieldTransform} self.transformMap = cameraGeom.TransformMap( self.nativeSys, transforms)
def __init__( self, name="detector 1", id=1, detType=SCIENCE, serial="xkcd722", bbox=None, # do not use mutable objects as defaults numAmps=3, pixelSize=(0.02, 0.02), ampExtent=(5, 6), orientation=Orientation(), plateScale=20.0, radialDistortion=0.925, crosstalk=None, modFunc=None, physicalType="CCD", ): # note that (0., 0.) for the reference position is the center of the # first pixel self.name = name self.id = int(id) self.type = detType self.serial = serial if bbox is None: bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(1024, 1048)) self.bbox = bbox self.pixelSize = lsst.geom.Extent2D(*pixelSize) self.ampExtent = lsst.geom.Extent2I(*ampExtent) self.plateScale = float(plateScale) self.radialDistortion = float(radialDistortion) schema = afwTable.AmpInfoTable.makeMinimalSchema() self.ampInfo = afwTable.AmpInfoCatalog(schema) for i in range(numAmps): record = self.ampInfo.addNew() ampName = "amp %d" % (i + 1, ) record.setName(ampName) record.setBBox( lsst.geom.Box2I(lsst.geom.Point2I(-1, 1), self.ampExtent)) record.setGain(1.71234e3) record.setReadNoise(0.521237e2) record.setReadoutCorner(afwTable.LL) record.setHasRawInfo(False) self.orientation = orientation # compute TAN_PIXELS transform pScaleRad = lsst.geom.arcsecToRad(self.plateScale) radialDistortCoeffs = [ 0.0, 1.0 / pScaleRad, 0.0, self.radialDistortion / pScaleRad ] focalPlaneToField = afwGeom.makeRadialTransform(radialDistortCoeffs) pixelToTanPixel = makePixelToTanPixel( bbox=self.bbox, orientation=self.orientation, focalPlaneToField=focalPlaneToField, pixelSizeMm=self.pixelSize, ) self.transMap = { FOCAL_PLANE: self.orientation.makePixelFpTransform(self.pixelSize), CameraSys(TAN_PIXELS, self.name): pixelToTanPixel, CameraSys(ACTUAL_PIXELS, self.name): afwGeom.makeRadialTransform([0, 0.95, 0.01]), } if crosstalk is None: crosstalk = [[0.0 for _ in range(numAmps)] for _ in range(numAmps)] self.crosstalk = crosstalk self.physicalType = physicalType if modFunc: modFunc(self) self.detector = Detector( self.name, self.id, self.type, self.serial, self.bbox, self.ampInfo, self.orientation, self.pixelSize, self.transMap, np.array(self.crosstalk, dtype=np.float32), self.physicalType, )
def __init__( self, name="detector 1", id=1, detType=SCIENCE, serial="xkcd722", bbox=None, # do not use mutable objects as defaults numAmps=3, pixelSize=(0.02, 0.02), ampExtent=(5, 6), orientation=Orientation(), plateScale=20.0, radialDistortion=0.925, crosstalk=None, modFunc=None, ): """!Construct a DetectorWrapper @param[in] name detector name @param[in] id detector ID (int) @param[in] detType detector type (an lsst.afw.cameraGeom.DetectorType) @param[in] serial serial "number" (a string) @param[in] bbox bounding box; defaults to (0, 0), (1024x1024) (an lsst.geom.Box2I) @param[in] numAmps number of amplifiers (int) @param[in] pixelSize pixel size (mm) (an lsst.geom.Point2D) @param[in] ampExtent dimensions of amplifier image bbox (an lsst.geom.Extent2I) @param[in] orientation orientation of CCC in focal plane (lsst.afw.cameraGeom.Orientation) @param[in] plateScale plate scale in arcsec/mm; 20.0 is for LSST @param[in] radialDistortion radial distortion, in mm/rad^2 (the r^3 coefficient of the radial distortion polynomial that converts FIELD_ANGLE in radians to FOCAL_PLANE in mm); 0.925 is the value Dave Monet measured for lsstSim data @param[in] crosstalk crosstalk coefficient matrix @param[in] modFunc a function that can modify attributes just before constructing the detector; modFunc receives one argument: a DetectorWrapper with all attributes except detector set. """ # note that (0., 0.) for the reference position is the center of the # first pixel self.name = name self.id = int(id) self.type = detType self.serial = serial if bbox is None: bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(1024, 1048)) self.bbox = bbox self.pixelSize = lsst.geom.Extent2D(*pixelSize) self.ampExtent = lsst.geom.Extent2I(*ampExtent) self.plateScale = float(plateScale) self.radialDistortion = float(radialDistortion) schema = afwTable.AmpInfoTable.makeMinimalSchema() self.ampInfo = afwTable.AmpInfoCatalog(schema) for i in range(numAmps): record = self.ampInfo.addNew() ampName = "amp %d" % (i + 1, ) record.setName(ampName) record.setBBox( lsst.geom.Box2I(lsst.geom.Point2I(-1, 1), self.ampExtent)) record.setGain(1.71234e3) record.setReadNoise(0.521237e2) record.setReadoutCorner(afwTable.LL) record.setHasRawInfo(False) self.orientation = orientation # compute TAN_PIXELS transform pScaleRad = lsst.geom.arcsecToRad(self.plateScale) radialDistortCoeffs = [ 0.0, 1.0 / pScaleRad, 0.0, self.radialDistortion / pScaleRad ] focalPlaneToField = afwGeom.makeRadialTransform(radialDistortCoeffs) pixelToTanPixel = makePixelToTanPixel( bbox=self.bbox, orientation=self.orientation, focalPlaneToField=focalPlaneToField, pixelSizeMm=self.pixelSize, ) self.transMap = { FOCAL_PLANE: self.orientation.makePixelFpTransform(self.pixelSize), CameraSys(TAN_PIXELS, self.name): pixelToTanPixel, CameraSys(ACTUAL_PIXELS, self.name): afwGeom.makeRadialTransform([0, 0.95, 0.01]), } if crosstalk is None: crosstalk = [[0.0 for _ in range(numAmps)] for _ in range(numAmps)] self.crosstalk = crosstalk if modFunc: modFunc(self) self.detector = Detector( self.name, self.id, self.type, self.serial, self.bbox, self.ampInfo, self.orientation, self.pixelSize, self.transMap, np.array(self.crosstalk, dtype=np.float32), )
def testMakeModifiedWcsWithActualPixels(self): """Test makeModifiedWcs on a SkyWcs that has an ACTUAL_PIXELS frame """ cdMatrix = makeCdMatrix(scale=self.scale) baseWcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix) # model actual pixels to pixels as an arbitrary zoom factor; # this is not realistic, but is fine for a unit test actualPixelsToPixels = TransformPoint2ToPoint2(ast.ZoomMap(2, 0.72)) originalWcs = addActualPixelsFrame(baseWcs, actualPixelsToPixels) originalFrameDict = originalWcs.getFrameDict() # make an arbitrary but reasonable transform to insert using makeModifiedWcs pixelTransform = makeRadialTransform([0.0, 1.0, 0.0, 0.0011 ]) # arbitrary but reasonable pixPointList = ( # arbitrary but reasonable Point2D(0.0, 0.0), Point2D(1000.0, 0.0), Point2D(0.0, 2000.0), Point2D(-1111.0, -2222.0), ) for modifyActualPixels in (True, False): modifiedWcs = makeModifiedWcs( pixelTransform=pixelTransform, wcs=originalWcs, modifyActualPixels=modifyActualPixels) modifiedFrameDict = modifiedWcs.getFrameDict() self.assertEqual( modifiedFrameDict.getFrame(modifiedFrameDict.BASE).domain, "ACTUAL_PIXELS") modifiedActualPixelsToPixels = \ TransformPoint2ToPoint2(modifiedFrameDict.getMapping("ACTUAL_PIXELS", "PIXELS")) modifiedPixelsToIwc = TransformPoint2ToPoint2( modifiedFrameDict.getMapping("PIXELS", "IWC")) # compare pixels to sky skyList = modifiedWcs.pixelToSky(pixPointList) if modifyActualPixels: desiredPixelsToSky = pixelTransform.then( originalWcs.getTransform()) else: originalPixelsToSky = \ TransformPoint2ToSpherePoint(originalFrameDict.getMapping("PIXELS", "SKY")) desiredPixelsToSky = actualPixelsToPixels.then( pixelTransform).then(originalPixelsToSky) desiredSkyList = desiredPixelsToSky.applyForward(pixPointList) self.assertSpherePointListsAlmostEqual(skyList, desiredSkyList) # compare ACTUAL_PIXELS to PIXELS and PIXELS to IWC if modifyActualPixels: # check that ACTUAL_PIXELS to PIXELS has been modified as expected desiredActualPixelsToPixels = pixelTransform.then( actualPixelsToPixels) self.assertPairListsAlmostEqual( modifiedActualPixelsToPixels.applyForward(pixPointList), desiredActualPixelsToPixels.applyForward(pixPointList)) # check that PIXELS to IWC is unchanged originalPixelsToIwc = TransformPoint2ToPoint2( originalFrameDict.getMapping("PIXELS", "IWC")) self.assertPairListsAlmostEqual( modifiedPixelsToIwc.applyForward(pixPointList), originalPixelsToIwc.applyForward(pixPointList)) else: # check that ACTUAL_PIXELS to PIXELS is unchanged self.assertPairListsAlmostEqual( actualPixelsToPixels.applyForward(pixPointList), actualPixelsToPixels.applyForward(pixPointList)) # check that PIXELS to IWC has been modified as expected desiredPixelsToIwc = TransformPoint2ToPoint2( pixelTransform.getMapping().then( originalFrameDict.getMapping("PIXELS", "IWC"))) self.assertPairListsAlmostEqual( modifiedPixelsToIwc.applyForward(pixPointList), desiredPixelsToIwc.applyForward(pixPointList)) self.checkNonFitsWcs(modifiedWcs)
def testSimpleCurvedFocalPlane(self): """Test a trivial curved focal plane with square pixels The CCD's lower left pixel is centered on the boresight pupil center = focal plane center CCD x is along focal plane x """ bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(1000, 1000)) pixelSizeMm = afwGeom.Extent2D(0.02, 0.02) plateScale = 25.0 # arcsec/mm yaw = 0 * afwGeom.degrees # focal-plane position of ref position on detector (mm) fpPosition = afwGeom.Point2D(0, 0) # ref position on detector (pos of lower left corner) refPoint = afwGeom.Point2D(0, 0) orientation = cameraGeom.Orientation( fpPosition, refPoint, yaw, ) pixelToFocalPlane = orientation.makePixelFpTransform(pixelSizeMm) plateScaleRad = afwGeom.Angle( # rad/mm plateScale, afwGeom.arcseconds).asRadians() focalPlaneToField = afwGeom.makeRadialTransform( (0.0, plateScaleRad, 0.0, 0.001 * plateScaleRad)) pixelToField = pixelToFocalPlane.then(focalPlaneToField) pixelToTanPixel = makePixelToTanPixel( bbox=bbox, orientation=orientation, focalPlaneToField=focalPlaneToField, pixelSizeMm=pixelSizeMm, ) # field center should be pixel position 0, 0 and tan pixel position 0, # 0 pixAtFieldCtr = pixelToField.applyInverse(afwGeom.Point2D(0, 0)) self.assertPairsAlmostEqual(pixAtFieldCtr, [0, 0]) tanPixAtFieldCr = pixelToTanPixel.applyForward(pixAtFieldCtr) self.assertPairsAlmostEqual(tanPixAtFieldCr, [0, 0]) # build same camera geometry transforms without optical distortion focalPlaneToFieldNoDistortion = afwGeom.makeRadialTransform( (0.0, plateScaleRad)) pixelToFieldNoDistortion = pixelToFocalPlane.then( focalPlaneToFieldNoDistortion) for x in (100, 200, 1000): for y in (100, 500, 800): pixPos = afwGeom.Point2D(x, y) tanPixPos = pixelToTanPixel.applyForward(pixPos) # pix to tan pix should be radial self.assertAlmostEqual( math.atan2(pixPos[1], pixPos[0]), math.atan2(tanPixPos[1], tanPixPos[0]), ) # for a given field angle (which, together with a pointing, gives a position on the sky): # - field angle to pixels gives pixPos # - undistorted field anle to pixels gives tanPixPos fieldPos = pixelToField.applyForward(pixPos) desTanPixPos = pixelToFieldNoDistortion.applyInverse(fieldPos) self.assertPairsAlmostEqual(desTanPixPos, tanPixPos)
def __init__(self, name="detector 1", id=1, detType=SCIENCE, serial="xkcd722", bbox=None, # do not use mutable objects as defaults numAmps=3, pixelSize=(0.02, 0.02), ampExtent=(5, 6), orientation=Orientation(), plateScale=20.0, radialDistortion=0.925, crosstalk=None, modFunc=None, physicalType="CCD", ): # note that (0., 0.) for the reference position is the center of the # first pixel self.name = name self.id = int(id) self.type = detType self.serial = serial if bbox is None: bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(1024, 1048)) self.bbox = bbox self.pixelSize = lsst.geom.Extent2D(*pixelSize) self.ampExtent = lsst.geom.Extent2I(*ampExtent) self.plateScale = float(plateScale) self.radialDistortion = float(radialDistortion) schema = afwTable.AmpInfoTable.makeMinimalSchema() self.ampInfo = afwTable.AmpInfoCatalog(schema) for i in range(numAmps): record = self.ampInfo.addNew() ampName = "amp %d" % (i + 1,) record.setName(ampName) record.setBBox(lsst.geom.Box2I(lsst.geom.Point2I(-1, 1), self.ampExtent)) record.setGain(1.71234e3) record.setReadNoise(0.521237e2) record.setReadoutCorner(afwTable.LL) record.setHasRawInfo(False) self.orientation = orientation # compute TAN_PIXELS transform pScaleRad = lsst.geom.arcsecToRad(self.plateScale) radialDistortCoeffs = [0.0, 1.0/pScaleRad, 0.0, self.radialDistortion/pScaleRad] focalPlaneToField = afwGeom.makeRadialTransform(radialDistortCoeffs) pixelToTanPixel = makePixelToTanPixel( bbox=self.bbox, orientation=self.orientation, focalPlaneToField=focalPlaneToField, pixelSizeMm=self.pixelSize, ) self.transMap = { FOCAL_PLANE: self.orientation.makePixelFpTransform(self.pixelSize), CameraSys(TAN_PIXELS, self.name): pixelToTanPixel, CameraSys(ACTUAL_PIXELS, self.name): afwGeom.makeRadialTransform([0, 0.95, 0.01]), } if crosstalk is None: crosstalk = [[0.0 for _ in range(numAmps)] for _ in range(numAmps)] self.crosstalk = crosstalk self.physicalType = physicalType if modFunc: modFunc(self) self.detector = Detector( self.name, self.id, self.type, self.serial, self.bbox, self.ampInfo, self.orientation, self.pixelSize, self.transMap, np.array(self.crosstalk, dtype=np.float32), self.physicalType, )
def __init__( self, name="detector 1", id=1, detType=DetectorType.SCIENCE, serial="xkcd722", bbox=None, # do not use mutable objects as defaults numAmps=3, pixelSize=(0.02, 0.02), ampExtent=(5, 6), orientation=Orientation(), plateScale=20.0, radialDistortion=0.925, crosstalk=None, modFunc=None, physicalType="CCD", cameraBuilder=None): # note that (0., 0.) for the reference position is the center of the # first pixel self.name = name self.id = int(id) self.type = detType self.serial = serial if bbox is None: bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(1024, 1048)) self.bbox = bbox self.pixelSize = lsst.geom.Extent2D(*pixelSize) self.ampExtent = lsst.geom.Extent2I(*ampExtent) self.plateScale = float(plateScale) self.orientation = orientation self.radialDistortion = float(radialDistortion) # compute TAN_PIXELS transform pScaleRad = lsst.geom.arcsecToRad(self.plateScale) radialDistortCoeffs = [ 0.0, 1.0 / pScaleRad, 0.0, self.radialDistortion / pScaleRad ] focalPlaneToField = afwGeom.makeRadialTransform(radialDistortCoeffs) pixelToTanPixel = makePixelToTanPixel( bbox=self.bbox, orientation=self.orientation, focalPlaneToField=focalPlaneToField, pixelSizeMm=self.pixelSize, ) tanPixelSys = CameraSys(TAN_PIXELS, self.name) actualPixelSys = CameraSys(ACTUAL_PIXELS, self.name) self.transMap = { FOCAL_PLANE: self.orientation.makePixelFpTransform(self.pixelSize), tanPixelSys: pixelToTanPixel, actualPixelSys: afwGeom.makeRadialTransform([0, 0.95, 0.01]), } if crosstalk is None: crosstalk = [[0.0 for _ in range(numAmps)] for _ in range(numAmps)] self.crosstalk = crosstalk self.physicalType = physicalType if cameraBuilder is None: cameraBuilder = Camera.Builder("CameraForDetectorWrapper") self.ampList = [] for i in range(numAmps): ampBuilder = Amplifier.Builder() ampName = f"amp {i + 1}" ampBuilder.setName(ampName) ampBuilder.setBBox( lsst.geom.Box2I(lsst.geom.Point2I(-1, 1), self.ampExtent)) ampBuilder.setGain(1.71234e3) ampBuilder.setReadNoise(0.521237e2) ampBuilder.setReadoutCorner(ReadoutCorner.LL) self.ampList.append(ampBuilder) if modFunc: modFunc(self) detectorBuilder = cameraBuilder.add(self.name, self.id) detectorBuilder.setType(self.type) detectorBuilder.setSerial(self.serial) detectorBuilder.setPhysicalType(self.physicalType) detectorBuilder.setBBox(self.bbox) detectorBuilder.setOrientation(self.orientation) detectorBuilder.setPixelSize(self.pixelSize) detectorBuilder.setTransformFromPixelsTo(tanPixelSys, self.transMap[tanPixelSys]) detectorBuilder.setTransformFromPixelsTo(actualPixelSys, self.transMap[actualPixelSys]) detectorBuilder.setCrosstalk(np.array(self.crosstalk, dtype=np.float32)) for ampBuilder in self.ampList: detectorBuilder.append(ampBuilder) camera = cameraBuilder.finish() self.detector = camera[self.name]