Coverage for tests / test_refit_pointing.py: 17%

103 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-14 23:59 +0000

1# This file is part of meas_astrom. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

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 GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22import unittest 

23import lsst.utils.tests 

24 

25from lsst.afw.geom import makeModifiedWcs, makeTransform 

26from lsst.afw.table import ExposureTable, ExposureCatalog 

27from lsst.geom import AffineTransform, LinearTransform, Box2D, SpherePoint, degrees 

28from lsst.meas.astrom.refit_pointing import RefitPointingConfig, RefitPointingTask 

29from lsst.obs.base.yamlCamera import makeCamera 

30from lsst.obs.base.utils import createInitialSkyWcsFromBoresight 

31from lsst.resources import ResourcePath 

32 

33 

34class RefitPointingTestCase(lsst.utils.tests.TestCase): 

35 """Tests for `RefitPointingTask`.""" 

36 

37 @classmethod 

38 def setUpClass(cls): 

39 with ResourcePath("resource://lsst.obs.base/test/dummycam.yaml").as_local() as camera_path: 

40 cls.camera = makeCamera(camera_path.ospath) 

41 assert len(cls.camera) == 2, "This is what the test expects." 

42 

43 def test_config_validate(self): 

44 """Test that the default config options are valid.""" 

45 config = RefitPointingConfig() 

46 config.validate() 

47 

48 def test_init(self): 

49 """Test that the task can be constructed and that it adds the expected 

50 schema columns. 

51 """ 

52 config = RefitPointingConfig() 

53 config.schema_prefix = "pre_" 

54 schema = ExposureTable.makeMinimalSchema() 

55 RefitPointingTask(config=config, schema=schema) 

56 self.assertIn("pre_wcs_detector_pointing_residual", schema) 

57 self.assertIn("pre_wcs_visit_pointing_residual", schema) 

58 self.assertIn("pre_wcs_detector_pointing_rejected", schema) 

59 

60 def test_ideal_run(self): 

61 """Test running the task with only good detectors with perfect 

62 camera-geometry-only WCSs to recover the boresight and orientation with 

63 zero residuals. 

64 """ 

65 schema = ExposureTable.makeMinimalSchema() 

66 task = RefitPointingTask(schema=schema) 

67 catalog = ExposureCatalog(schema) 

68 boresight = SpherePoint(45.0, 60.0, degrees) 

69 orientation = 30.0*degrees 

70 for detector in self.camera: 

71 record = catalog.addNew() 

72 record.setId(detector.getId()) 

73 record.setBBox(detector.getBBox()) 

74 record.setWcs(createInitialSkyWcsFromBoresight(boresight, orientation, detector)) 

75 result = task.run(catalog=catalog, camera=self.camera) 

76 # Check that we recover the input pointing. 

77 self.assertAlmostEqual(result.regions.boresight_ra, boresight.getRa().asDegrees()) 

78 self.assertAlmostEqual(result.regions.boresight_dec, boresight.getDec().asDegrees()) 

79 self.assertAlmostEqual(result.regions.orientation, orientation.asDegrees()) 

80 # Test that the residuals are zero, since the WCSs are idealized. 

81 self.assertAlmostEqual(catalog[0]["wcs_detector_pointing_residual"].asArcseconds(), 0.0) 

82 self.assertAlmostEqual(catalog[1]["wcs_detector_pointing_residual"].asArcseconds(), 0.0) 

83 self.assertAlmostEqual(catalog[0]["wcs_visit_pointing_residual"].asArcseconds(), 0.0) 

84 self.assertAlmostEqual(catalog[1]["wcs_visit_pointing_residual"].asArcseconds(), 0.0) 

85 self.assertFalse(catalog[0]["wcs_detector_pointing_rejected"]) 

86 self.assertFalse(catalog[1]["wcs_detector_pointing_rejected"]) 

87 # Test that the output regions include points inside the detectors. 

88 for record in catalog: 

89 pixel_bbox = Box2D(record.getBBox()) 

90 pixel_bbox.grow(-1E-6) # shrink to avoid floating-point equality issues 

91 for test_point in record.getWcs().pixelToSky(pixel_bbox.getCorners()): 

92 test_vec = test_point.getVector() 

93 self.assertTrue(result.regions.visit_region.contains(test_vec)) 

94 self.assertTrue(result.regions.detector_regions[record.getId()].contains(test_vec)) 

95 

96 def test_rejection_run(self): 

97 """Test running the task with one good detector with a perfect 

98 camera-geometry-only WCS and one detector with a garbage WCS, to make 

99 sure the latter is rejected. 

100 """ 

101 schema = ExposureTable.makeMinimalSchema() 

102 task = RefitPointingTask(schema=schema) 

103 catalog = ExposureCatalog(schema) 

104 boresight = SpherePoint(45.0, 60.0, degrees) 

105 orientation = 30.0*degrees 

106 good_id = None 

107 bad_id = None 

108 correct_wcs = None 

109 for detector in self.camera: 

110 record = catalog.addNew() 

111 record.setId(detector.getId()) 

112 record.setBBox(detector.getBBox()) 

113 if good_id is None: 

114 good_id = detector.getId() 

115 record.setWcs(createInitialSkyWcsFromBoresight(boresight, orientation, detector)) 

116 else: 

117 bad_id = detector.getId() 

118 correct_wcs = createInitialSkyWcsFromBoresight(boresight, orientation, detector) 

119 bad_wcs = makeModifiedWcs( 

120 makeTransform(AffineTransform(LinearTransform.makeScaling(1.5, 0.75))), 

121 correct_wcs, 

122 False 

123 ) 

124 record.setWcs(bad_wcs) 

125 result = task.run(catalog=catalog, camera=self.camera) 

126 # Test that the residuals are large for the bad detector, and that we 

127 # rejected it, and that we nulled that WCS. 

128 self.assertGreater(catalog[bad_id]["wcs_detector_pointing_residual"].asArcseconds(), 10.0) 

129 self.assertGreater(catalog[bad_id]["wcs_visit_pointing_residual"].asArcseconds(), 10.0) 

130 self.assertTrue(catalog[bad_id]["wcs_detector_pointing_rejected"]) 

131 self.assertIsNone(catalog[bad_id].getWcs()) 

132 # Check that we recover the input pointing (one detector is 

133 # sufficient). 

134 self.assertAlmostEqual(result.regions.boresight_ra, boresight.getRa().asDegrees()) 

135 self.assertAlmostEqual(result.regions.boresight_dec, boresight.getDec().asDegrees()) 

136 self.assertAlmostEqual(result.regions.orientation, orientation.asDegrees()) 

137 # Test that the residuals are zero for the good detector. 

138 self.assertAlmostEqual(catalog[good_id]["wcs_detector_pointing_residual"].asArcseconds(), 0.0) 

139 self.assertAlmostEqual(catalog[good_id]["wcs_visit_pointing_residual"].asArcseconds(), 0.0) 

140 self.assertFalse(catalog[good_id]["wcs_detector_pointing_rejected"]) 

141 # Test that the output regions include points inside the detectors. 

142 for record in catalog: 

143 pixel_bbox = Box2D(record.getBBox()) 

144 pixel_bbox.grow(-1E-6) # shrink to avoid floating-point equality issues 

145 if record.getId() == good_id: 

146 wcs = record.getWcs() 

147 else: 

148 wcs = correct_wcs 

149 # Grow the box to test that we padded this less-trustworthy 

150 # region. 

151 pixel_bbox.grow(50.0) 

152 for test_point in wcs.pixelToSky(pixel_bbox.getCorners()): 

153 test_vec = test_point.getVector() 

154 self.assertTrue(result.regions.visit_region.contains(test_vec)) 

155 self.assertTrue(result.regions.detector_regions[record.getId()].contains(test_vec)) 

156 

157 

158class MemoryTester(lsst.utils.tests.MemoryTestCase): 

159 pass 

160 

161 

162def setup_module(module): 

163 lsst.utils.tests.init() 

164 

165 

166if __name__ == "__main__": 166 ↛ 167line 166 didn't jump to line 167 because the condition on line 166 was never true

167 lsst.utils.tests.init() 

168 unittest.main()