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
« 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/>.
22import unittest
23import lsst.utils.tests
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
34class RefitPointingTestCase(lsst.utils.tests.TestCase):
35 """Tests for `RefitPointingTask`."""
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."
43 def test_config_validate(self):
44 """Test that the default config options are valid."""
45 config = RefitPointingConfig()
46 config.validate()
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)
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))
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))
158class MemoryTester(lsst.utils.tests.MemoryTestCase):
159 pass
162def setup_module(module):
163 lsst.utils.tests.init()
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()