Skip to content

Commit

Permalink
Add image.utils.getDistortedWcs and a test for it
Browse files Browse the repository at this point in the history
Added a new function lsst.image.utils.getDistortedWcs.
Added a unit test for it. To write the unit test I added a new "modFunc" argument to
cameraGeom.getUtils.DetectorWraper.__init__ to allow flexibile modification of wrapper
attributes just before constructing the Detector. Finally, I used that new modFunc argument
to replace two other arguments and updated the test that used those other arguments
to use modFunc instead.

Switch from using cast_TanWcs to TanWcs.cast

Fix a typo in a doc string.
  • Loading branch information
r-owen committed Apr 3, 2015
1 parent adeac45 commit 3937a7a
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 11 deletions.
13 changes: 5 additions & 8 deletions python/lsst/afw/cameraGeom/testUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ def __init__(self,
orientation=Orientation(),
plateScale=20.0,
radialDistortion=0.925,
tryDuplicateAmpNames=False,
tryBadCameraSys=False,
modFunc=None,
):
"""!Construct a DetectorWrapper
Expand All @@ -49,8 +48,8 @@ def __init__(self,
(the r^3 coefficient of the radial distortion polynomial
that converts PUPIL in radians to FOCAL_PLANE in mm);
0.925 is the value Dave Monet measured for lsstSim data
@param[in] tryDuplicateAmpNames create 2 amps with the same name (should result in an error)
@param[in] tryBadCameraSys add a transform for an unsupported coord. system (should result in an error)
@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
Expand All @@ -69,8 +68,6 @@ def __init__(self,
for i in range(numAmps):
record = self.ampInfo.addNew()
ampName = "amp %d" % (i + 1,)
if i == 1 and tryDuplicateAmpNames:
ampName = self.ampInfo[0].getName()
record.setName(ampName)
record.setBBox(afwGeom.Box2I(afwGeom.Point2I(-1, 1), self.ampExtent))
record.setGain(1.71234e3)
Expand All @@ -96,8 +93,8 @@ def __init__(self,
CameraSys(TAN_PIXELS, self.name): pixelToTanPixel,
CameraSys(ACTUAL_PIXELS, self.name): afwGeom.RadialXYTransform([0, 0.95, 0.01]),
}
if tryBadCameraSys:
self.transMap[CameraSys("foo", "wrong detector")] = afwGeom.IdentityXYTransform()
if modFunc:
modFunc(self)
self.detector = Detector(
self.name,
self.id,
Expand Down
36 changes: 36 additions & 0 deletions python/lsst/afw/image/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import re
import lsst.pex.policy as pexPolicy
from lsst.afw.cameraGeom import TAN_PIXELS
import lsst.afw.detection as afwDetect
from . import imageLib as afwImage

Expand All @@ -42,6 +43,41 @@ def clipImage(im, minClip, maxClip):
ds = afwDetect.FootprintSet(mi, afwDetect.Threshold(maxClip))
afwDetect.setImageFromFootprintList(mi.getImage(), ds.getFootprints(), maxClip)

def getDistortedWcs(exposureInfo, log=None):
"""!Get a WCS from an exposureInfo, with distortion terms if possible
If the WCS in the exposure is a pure TAN WCS and distortion information is available
in the exposure's Detector, then return a DistortedTanWcs that combines the
distortion information with the pure TAN WCS.
Otherwise return the WCS in the exposureInfo without modification.
This function is intended as a temporary workaround until ISR puts a WCS with distortion information
into its exposures.
@param[in] exposureInfo exposure information (an lsst.afw.image.ExposureInfo),
e.g. from exposure.getInfo()
@param[in] log an lsst.pex.logging.Log or None; if specified then a warning is logged if:
- the exposureInfo's WCS has no distortion and cannot be cast to a TanWcs
- the expousureInfo's detector has no TAN_PIXELS transform (distortion information)
@throw RuntimeError if exposureInfo has no WCS.
"""
if not exposureInfo.hasWcs():
raise RuntimeError("exposure must have a WCS to use as an initial guess")
wcs = exposureInfo.getWcs()
if not wcs.hasDistortion() and exposureInfo.hasDetector():
# warn but continue if TAN_PIXELS not present or the initial WCS is not a TanWcs;
# other errors indicate a bug that should raise an exception
detector = exposureInfo.getDetector()
try:
pixelsToTanPixels = detector.getTransform(TAN_PIXELS)
tanWcs = afwImage.TanWcs.cast(wcs)
except Exception as e:
if log:
log.warn("Could not create a DistortedTanWcs: %s" % (e,))
else:
wcs = afwImage.DistortedTanWcs(tanWcs, pixelsToTanPixels)
return wcs

def resetFilters():
"""Reset registry of filters and filter properties"""
afwImage.Filter.reset()
Expand Down
11 changes: 9 additions & 2 deletions tests/testDetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,15 @@ def testBasics(self):
def testConstructorErrors(self):
"""Test constructor errors
"""
self.assertRaises(lsst.pex.exceptions.Exception, DetectorWrapper, tryDuplicateAmpNames=True)
self.assertRaises(lsst.pex.exceptions.Exception, DetectorWrapper, tryBadCameraSys=True)
def duplicateAmpName(dw):
"""Set two amplifiers to the same name"""
dw.ampInfo[1].setName(dw.ampInfo[0].getName())
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()
self.assertRaises(lsst.pex.exceptions.Exception, DetectorWrapper, modFunc=addBadCameraSys)

def testTransform(self):
"""Test the transform method
Expand Down
67 changes: 66 additions & 1 deletion tests/testDistortedTanWcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
# the GNU General Public License along with this program. If not,
# see <http://www.lsstcorp.org/LegalNotices/>.
#

import unittest

from lsst.afw.cameraGeom import TAN_PIXELS
from lsst.afw.cameraGeom.testUtils import DetectorWrapper
import lsst.afw.image as afwImage
import lsst.afw.geom as afwGeom
from lsst.afw.image.utils import getDistortedWcs
import lsst.utils.tests as utilsTests
import lsst.daf.base as dafBase

Expand Down Expand Up @@ -109,6 +111,69 @@ def testTransform(self):
for i in range(2):
self.assertAlmostEqual(pixPos[i], pixPosRoundTrip[i])

def testGetDistortedWcs(self):
"""Test utils.getDistortedWcs
"""
dw = DetectorWrapper()
detector = dw.detector

# the standard case: the exposure's WCS is pure TAN WCS and distortion information is available;
# return a DistortedTanWcs
exposure = afwImage.ExposureF(10, 10)
exposure.setDetector(detector)
exposure.setWcs(self.tanWcs)
self.assertFalse(self.tanWcs.hasDistortion())
outWcs = getDistortedWcs(exposure.getInfo())
self.assertTrue(outWcs.hasDistortion())
self.assertTrue(afwImage.DistortedTanWcs.cast(outWcs) is not None)
del exposure # avoid accidental reuse
del outWcs

# return the original WCS if the exposure's WCS has distortion
pixelsToTanPixels = afwGeom.RadialXYTransform([0, 1.001, 0.00003])
distortedWcs = afwImage.DistortedTanWcs(self.tanWcs, pixelsToTanPixels)
self.assertTrue(distortedWcs.hasDistortion())
exposure = afwImage.ExposureF(10, 10)
exposure.setWcs(distortedWcs)
exposure.setDetector(detector)
outWcs = getDistortedWcs(exposure.getInfo())
self.assertTrue(outWcs.hasDistortion())
self.assertTrue(afwImage.DistortedTanWcs.cast(outWcs) is not None)
del exposure
del distortedWcs
del outWcs

# raise an exception if exposure has no WCS
exposure = afwImage.ExposureF(10, 10)
exposure.setDetector(detector)
self.assertRaises(Exception, getDistortedWcs, exposure.getInfo())
del exposure

# return the original pure TAN WCS if the exposure has no detector
exposure = afwImage.ExposureF(10, 10)
exposure.setWcs(self.tanWcs)
outWcs = getDistortedWcs(exposure.getInfo())
self.assertFalse(outWcs.hasDistortion())
self.assertTrue(afwImage.TanWcs.cast(outWcs) is not None)
self.assertTrue(afwImage.DistortedTanWcs.cast(outWcs) is None)
del exposure
del outWcs

# return the original pure TAN WCS if the exposure's detector has no TAN_PIXELS transform
def removeTanPixels(detectorWrapper):
tanPixSys = detector.makeCameraSys(TAN_PIXELS)
detectorWrapper.transMap.pop(tanPixSys)
detectorNoTanPix = DetectorWrapper(modFunc=removeTanPixels).detector
exposure = afwImage.ExposureF(10, 10)
exposure.setWcs(self.tanWcs)
exposure.setDetector(detectorNoTanPix)
outWcs = getDistortedWcs(exposure.getInfo())
self.assertFalse(outWcs.hasDistortion())
self.assertTrue(afwImage.TanWcs.cast(outWcs) is not None)
self.assertTrue(afwImage.DistortedTanWcs.cast(outWcs) is None)
del exposure
del outWcs

def suite():
"""Returns a suite containing all the test cases in this module."""
utilsTests.init()
Expand Down

0 comments on commit 3937a7a

Please sign in to comment.