Coverage for tests/test_distortion.py: 24%

67 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-12 03:07 -0700

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 

27 

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 

36 

37DataFileName = "data/distortionData.pickle" # path relative to the tests directory 

38 

39 

40class HscDistortionTestCase(lsst.utils.tests.TestCase): 

41 """Testing HscDistortion implementation 

42 

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 

49 

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! 

53 

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) 

58 

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) 

63 

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) 

67 

68 fieldAngleToFocalPlaneTolerance = transformRegistry["hsc"].ConfigClass().tolerance 

69 

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) 

93 

94 def makeDistortionData(self): 

95 """Make distortion data 

96 

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 ) 

142 

143 data[detector.getName()] = Struct(serial=detector.getSerial(), cornerDict=cornerDict) 

144 

145 return data 

146 

147 def toKey(self, pixPos): 

148 return "(%0.1f, %0.1f)" % tuple(pixPos) 

149 

150 

151class TestMemory(lsst.utils.tests.MemoryTestCase): 

152 def setUp(self): 

153 lsst.utils.tests.MemoryTestCase.setUp(self) 

154 

155 

156def setup_module(module): 

157 lsst.utils.tests.init() 

158 

159 

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()