Coverage for tests/test_distortion.py: 24%
67 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-15 02:55 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-15 02:55 -0800
1#
2# LSST Data Management System
3#
4# Copyright 2008-2016 AURA/LSST.
5#
6# This product includes software developed by the
7# LSST Project (http://www.lsst.org/).
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the LSST License Statement and
20# the GNU General Public License along with this program. If not,
21# see <https://www.lsstcorp.org/LegalNotices/>.
22#
23import math
24import os.path
25import unittest
26import pickle
28import lsst.utils.tests
29from lsst.pipe.base import Struct
30from lsst.afw.geom import transformRegistry
31from lsst.afw.cameraGeom import FOCAL_PLANE, FIELD_ANGLE, PIXELS
32from lsst.obs.subaru import HyperSuprimeCam
33# Set SAVE_DATA True to save new distortion data; this will make the test fail,
34# to remind you to set it False before committing the code.
35SAVE_DATA = False
37DataFileName = "data/distortionData.pickle" # path relative to the tests directory
40class HscDistortionTestCase(lsst.utils.tests.TestCase):
41 """Testing HscDistortion implementation
43 HscDistortion is based on the HSC package "distEst". We
44 test that it produces the same results.
45 """
46 def testDistortion(self):
47 """Test that the distortion data matches the saved data or create new
48 data
50 If SAVE_DATA is true then save newly created data and then fail the
51 test in order to prevent anyone from committing the test with SAVE_DATA
52 true!
54 Otherwise create new data and compare to the saved data
55 """
56 newData = self.makeDistortionData()
57 dataPath = os.path.join(os.path.dirname(__file__), DataFileName)
59 if SAVE_DATA:
60 with open(dataPath, "wb") as dataFile:
61 pickle.dump(newData, dataFile, protocol=2)
62 self.fail("Saved new data to %r; please set SAVE_DATA back to False" % dataPath)
64 if not os.path.exists(dataPath):
65 self.fail("Cannot find saved data %r; set SAVE_DATA = True and run again to save new data" %
66 dataPath)
68 fieldAngleToFocalPlaneTolerance = transformRegistry["hsc"].ConfigClass().tolerance
70 with open(dataPath, "rb") as dataFile:
71 savedData = pickle.load(dataFile)
72 maxRoundTripFocalPlaneError = 0
73 maxRoundTripPixPosError = 0
74 for detectorName, ccdData in newData.items():
75 savedCcdData = savedData[detectorName]
76 self.assertEqual(ccdData.serial, savedCcdData.serial)
77 for pixPosKey, cornerData in ccdData.cornerDict.items():
78 savedCornerData = savedCcdData.cornerDict[pixPosKey]
79 self.assertEqual(cornerData.pixPos, savedCornerData.pixPos)
80 self.assertPairsAlmostEqual(cornerData.focalPlane, savedCornerData.focalPlane)
81 self.assertPairsAlmostEqual(cornerData.fieldAngle, savedCornerData.fieldAngle)
82 maxRoundTripFocalPlaneError = max(
83 maxRoundTripFocalPlaneError,
84 math.hypot(*(cornerData.focalPlaneRoundTrip - cornerData.focalPlane))
85 )
86 self.assertPairsAlmostEqual(cornerData.focalPlaneRoundTrip, cornerData.focalPlane,
87 maxDiff=fieldAngleToFocalPlaneTolerance)
88 maxRoundTripPixPosError = max(maxRoundTripPixPosError,
89 math.hypot(*(cornerData.pixPosRoundTrip - cornerData.pixPos)))
90 self.assertPairsAlmostEqual(cornerData.pixPosRoundTrip, cornerData.pixPos)
91 print("maxRoundTripFocalPlaneError =", maxRoundTripFocalPlaneError)
92 print("maxRoundTripPixPosError =", maxRoundTripPixPosError)
94 def makeDistortionData(self):
95 """Make distortion data
97 The data format is a dict of detector name: ccdData, where
98 ccdData is a Struct containing these fields:
99 - serial: detector.getSerial
100 - cornerDict: a dict of pixPosKey, cornerData, where:
101 - pixPosKey: self.asKey(pixPos) where pixPos is pixel position
102 - cornerData is Struct contains these fields (all of
103 type lsst.geom.Point2D):
104 - pixPos: pixel position
105 - focalPlane: focal plane position computed from pixPos
106 - fieldAngle: fieldAngle position computed from focalPlane
107 - focalPlaneRoundTrip: focal plane position computed from
108 fieldAngle
109 - pixPosRoundTrip: pixel position computed from focalPlane
110 """
111 hsc = HyperSuprimeCam()
112 camera = hsc.getCamera()
113 focalPlaneToFieldAngle = camera.getTransformMap().getTransform(FOCAL_PLANE, FIELD_ANGLE)
114 data = {} # dict of detector name: CcdData
115 for detector in camera:
116 # for each corner of each CCD:
117 # - get pixel position
118 # - convert to focal plane coordinates using the detector and
119 # record it
120 # - convert to field angle (this is the conversion that uses
121 # HscDistortion) and record it
122 # - convert back to focal plane (testing inverse direction of
123 # HscDistortion) and record it
124 # - convert back to pixel position and record it; pixel <-> focal
125 # plane is affine so there is no reason to doubt the inverse
126 # transform, but there is no harm
127 pixelsToFocalPlane = detector.getTransform(PIXELS, FOCAL_PLANE)
128 cornerDict = {}
129 for pixPos in detector.getCorners(PIXELS):
130 pixPos = pixPos
131 focalPlane = pixelsToFocalPlane.applyForward(pixPos)
132 fieldAngle = focalPlaneToFieldAngle.applyForward(focalPlane)
133 focalPlaneRoundTrip = focalPlaneToFieldAngle.applyInverse(fieldAngle)
134 pixPosRoundTrip = pixelsToFocalPlane.applyInverse(focalPlane)
135 cornerDict[self.toKey(pixPos)] = Struct(
136 pixPos = pixPos,
137 focalPlane = focalPlane,
138 fieldAngle = fieldAngle,
139 focalPlaneRoundTrip = focalPlaneRoundTrip,
140 pixPosRoundTrip = pixPosRoundTrip,
141 )
143 data[detector.getName()] = Struct(serial=detector.getSerial(), cornerDict=cornerDict)
145 return data
147 def toKey(self, pixPos):
148 return "(%0.1f, %0.1f)" % tuple(pixPos)
151class TestMemory(lsst.utils.tests.MemoryTestCase):
152 def setUp(self):
153 lsst.utils.tests.MemoryTestCase.setUp(self)
156def setup_module(module):
157 lsst.utils.tests.init()
160if __name__ == "__main__": 160 ↛ 161line 160 didn't jump to line 161, because the condition on line 160 was never true
161 lsst.utils.tests.init()
162 unittest.main()