Coverage for tests/test_jointcal_cfht.py : 20%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of jointcal.
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 os
24import tempfile
26from astropy import units as u
28import lsst.geom
29import lsst.utils
30import lsst.pex.exceptions
31import lsst.pex.config
33import jointcalTestBase
36# for MemoryTestCase
37def setup_module(module):
38 lsst.utils.tests.init()
41class JointcalTestCFHT(jointcalTestBase.JointcalTestBase, lsst.utils.tests.TestCase):
43 @classmethod
44 def setUpClass(cls):
45 try:
46 cls.data_dir = lsst.utils.getPackageDir('testdata_jointcal')
47 except lsst.pex.exceptions.NotFoundError:
48 raise unittest.SkipTest("testdata_jointcal not setup")
50 def setUp(self):
51 # We don't want the absolute astrometry to become significantly worse
52 # than the single-epoch astrometry (about 0.040").
53 # See Readme for an explanation of this empirical value.
54 self.dist_rms_absolute = 49e-3*u.arcsecond
56 do_plot = False
58 # center of the cfht validation_data catalog
59 center = lsst.geom.SpherePoint(214.884832, 52.6622199, lsst.geom.degrees)
60 radius = 3*lsst.geom.degrees
62 input_dir = os.path.join(self.data_dir, 'cfht')
63 all_visits = [849375, 850587]
65 self.setUp_base(center, radius,
66 input_dir=input_dir,
67 all_visits=all_visits,
68 do_plot=do_plot,
69 log_level="DEBUG")
71 def test_jointcalTask_2_visits(self):
72 """Test the simple models with two visits and check that some debug
73 output files also get created.
74 """
75 self.config = lsst.jointcal.jointcal.JointcalConfig()
76 self.config.astrometryModel = "simple"
77 self.config.photometryModel = "simpleFlux"
78 self.config.writeInitialModel = True # write the initial models
79 # to test whether we got the expected chi2 contribution files.
80 self.config.writeChi2FilesInitialFinal = True
81 # use a temporary directory for debug output, to prevent test collisions
82 with tempfile.TemporaryDirectory() as tempdir:
83 self.config.debugOutputPath = tempdir
85 # See Readme for an explanation of these empirical values.
86 dist_rms_relative = 11e-3*u.arcsecond
87 pa1 = 0.014
88 metrics = {'collected_astrometry_refStars': 1770,
89 'collected_photometry_refStars': 1770,
90 'selected_astrometry_refStars': 747,
91 'selected_photometry_refStars': 747,
92 'associated_astrometry_fittedStars': 2269,
93 'associated_photometry_fittedStars': 2269,
94 'selected_astrometry_fittedStars': 1408,
95 'selected_photometry_fittedStars': 1408,
96 'selected_astrometry_ccdImages': 12,
97 'selected_photometry_ccdImages': 12,
98 'astrometry_final_chi2': 1609.29,
99 'astrometry_final_ndof': 3332,
100 'photometry_final_chi2': 3632.26,
101 'photometry_final_ndof': 1693
102 }
104 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, pa1, metrics=metrics)
106 # Check for the existence of the chi2 contribution files.
107 expected = ['photometry_initial_chi2-0_r', 'astrometry_initial_chi2-0_r',
108 'photometry_final_chi2-0_r', 'astrometry_final_chi2-0_r']
109 for partial in expected:
110 name = os.path.join(tempdir, partial+'-ref.csv')
111 self.assertTrue(os.path.exists(name), msg="Did not find file %s"%name)
112 name = os.path.join(tempdir, partial+'-meas.csv')
113 self.assertTrue(os.path.exists(name), msg='Did not find file %s'%name)
115 expected = ["initialAstrometryModel.txt", "initialPhotometryModel.txt"]
116 for name in expected:
117 fullpath = os.path.join(tempdir, name)
118 self.assertTrue(os.path.exists(fullpath), msg=f"Did not find file {fullpath}")
120 def setup_jointcalTask_2_visits_constrainedAstrometry(self):
121 """Set default values for the constrainedAstrometry tests, and make
122 the differences between each test and the defaults more obvious.
123 """
124 self.config = lsst.jointcal.jointcal.JointcalConfig()
125 self.config.astrometryModel = "constrained"
126 self.config.doPhotometry = False
127 self.jointcalStatistics.do_photometry = False
129 # See Readme for an explanation of these empirical values.
130 dist_rms_relative = 12e-3*u.arcsecond
131 metrics = {'collected_astrometry_refStars': 1770,
132 'selected_astrometry_refStars': 747,
133 'associated_astrometry_fittedStars': 2269,
134 'selected_astrometry_fittedStars': 1408,
135 'selected_astrometry_ccdImages': 12,
136 'astrometry_final_chi2': 1714.8,
137 'astrometry_final_ndof': 3434,
138 }
140 return dist_rms_relative, metrics
142 def test_jointcalTask_2_visits_constrainedAstrometry_no_photometry(self):
143 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry()
144 self.config.writeInitialModel = True # write the initial models
145 # use a temporary directory for debug output, to prevent test collisions
146 # use a temporary directory for debug output, to prevent test collisions
147 with tempfile.TemporaryDirectory() as tempdir:
148 self.config.debugOutputPath = tempdir
150 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, None, metrics=metrics)
151 filename = os.path.join(tempdir, "initialAstrometryModel.txt")
152 self.assertTrue(os.path.exists(filename), msg=f"Did not find file {filename}")
154 def test_jointcalTask_2_visits_constrainedAstrometry_no_rank_update(self):
155 """Demonstrate that skipping the rank update doesn't substantially affect astrometry.
156 """
157 relative_error, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry()
158 self.config.astrometryDoRankUpdate = False
160 self._testJointcalTask(2, relative_error, self.dist_rms_absolute, None, metrics=metrics)
162 def test_jointcalTask_2_visits_constrainedAstrometry_4sigma_outliers(self):
163 """4 sigma outlier rejection means fewer available sources after the
164 fitter converges, resulting in a smaller ndof and chi2.
165 """
166 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry()
167 self.config.outlierRejectSigma = 4
168 metrics['astrometry_final_chi2'] = 1288.64
169 metrics['astrometry_final_ndof'] = 3232
171 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, None, metrics=metrics)
173 def test_jointcalTask_2_visits_constrainedAstrometry_astrometryReferenceUncertainty_smaller(self):
174 """Test with a smaller fake reference uncertainty: chi2 will be higher."""
175 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry()
176 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'),
177 'tests/config/astrometryReferenceErr-config.py')
178 self.configfiles.append(test_config)
179 metrics['astrometry_final_chi2'] = 11522.9
180 metrics['astrometry_final_ndof'] = 3406
182 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, None, metrics=metrics)
184 def test_jointcalTask_2_visits_constrainedAstrometry_astrometryReferenceUncertainty_None_fails(self):
185 """The default `None` should fail for the existing refcats that have no position errors."""
186 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry()
187 # This is the default, but we override it in tests/config/config.py,
188 # because none of the existing test refcats have errors. So we have to
189 # re-override it here.
190 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'),
191 'tests/config/astrometryReferenceErr-None-config.py')
192 self.configfiles.append(test_config)
193 with self.assertRaises(lsst.pex.config.FieldValidationError):
194 self._testJointcalTask(2, None, None, None)
196 def setup_jointcalTask_2_visits_constrainedPhotometry(self):
197 """Set default values for the constrainedPhotometry tests, and make
198 the differences between each test and the defaults more obvious.
199 """
200 self.config = lsst.jointcal.jointcal.JointcalConfig()
201 self.config.photometryModel = "constrainedFlux"
202 self.config.doAstrometry = False
203 self.jointcalStatistics.do_astrometry = False
205 # See Readme for an explanation of these empirical values.
206 pa1 = 0.017
207 metrics = {'collected_photometry_refStars': 1770,
208 'selected_photometry_refStars': 747,
209 'associated_photometry_fittedStars': 2269,
210 'selected_photometry_fittedStars': 1408,
211 'selected_photometry_ccdImages': 12,
212 'photometry_final_chi2': 3292.08,
213 'photometry_final_ndof': 1622
214 }
215 return pa1, metrics
217 def test_jointcalTask_2_visits_constrainedPhotometry_no_astrometry(self):
218 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
219 self.config.writeInitialModel = True # write the initial models
220 # use a temporary directory for debug output, to prevent test collisions
221 with tempfile.TemporaryDirectory() as tempdir:
222 self.config.debugOutputPath = tempdir
224 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
225 filename = os.path.join(tempdir, "initialPhotometryModel.txt")
226 self.assertTrue(os.path.exists(filename), msg=f"Did not find file {filename}")
228 def test_jointcalTask_2_visits_constrainedPhotometry_no_rank_update(self):
229 """Demonstrate that skipping the rank update doesn't substantially affect photometry.
230 """
231 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
232 self.config.photometryDoRankUpdate = False
234 # The constrainedPhotometry model is not purely linear, so a small
235 # change in final chi2 is possible.
236 metrics['photometry_final_chi2'] = 3297.34
238 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
240 def test_jointcalTask_2_visits_constrainedPhotometry_lineSearch(self):
241 """Activating the line search should only slightly change the chi2.
242 """
243 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
244 self.config.allowLineSearch = True
246 # Activating line search for constrainedPhotometry should result in
247 # nearly the same final fit (the system is somewhat non-linear, so it
248 # may not be exactly the same: check the "Line search scale factor"
249 # lines in the DEBUG log for values that are not ~1 for proof).
250 metrics['photometry_final_chi2'] = 3324.2
251 metrics['photometry_final_ndof'] = 1625
253 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
255 def test_jointcalTask_2_visits_constrainedPhotometry_flagged(self):
256 """Test the use of the FlaggedSourceSelector."""
257 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
258 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'),
259 'tests/config/cfht-flagged-config.py')
260 self.configfiles.append(test_config)
261 # Reduce warnings due to flaggedSourceSelector having fewer sources than astrometrySourceSelector.
262 self.config.minMeasuredStarsPerCcd = 30
263 self.config.minRefStarsPerCcd = 20
265 pa1 = 0.026
266 metrics['selected_photometry_refStars'] = 214
267 metrics['associated_photometry_fittedStars'] = 270
268 metrics['selected_photometry_fittedStars'] = 245
269 metrics['photometry_final_chi2'] = 373.141
270 metrics['photometry_final_ndof'] = 254
272 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
274 def test_jointcalTask_2_visits_constrainedMagnitude_no_astrometry(self):
275 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
276 self.config.photometryModel = "constrainedMagnitude"
278 # The resulting fit should be close to the constrainedFlux model:
279 # there are few CCDs and 2 visits, so there's not a lot of complexity
280 # in this case to distinguish the flux vs. magnitude models.
281 metrics['photometry_final_chi2'] = 3332.76
283 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
285 def test_jointcalTask_2_visits_constrainedFlux_pedestal(self):
286 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
287 self.config.photometryErrorPedestal = 0.02
289 # We're allowing more error in the fit, so PA1 may be worse.
290 pa1 = 0.021
291 # Final chi2 is much lower, because all sources contribute more error.
292 metrics['photometry_final_chi2'] = 2246.58
293 # ndof shouldn't change much; slightly different likelihood contours
294 metrics['photometry_final_ndof'] = 1624
296 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
298 def test_jointcalTask_2_visits_constrainedMagnitude_pedestal(self):
299 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
300 self.config.photometryModel = "constrainedMagnitude"
301 self.config.photometryErrorPedestal = 0.02
303 # We're allowing more error in the fit, so PA1 may be worse.
304 pa1 = 0.024
305 # Final chi2 is much lower, because all sources contribute more error.
306 metrics['photometry_final_chi2'] = 2243.56
307 # ndof shouldn't change much; slightly different likelihood contours
308 metrics['photometry_final_ndof'] = 1617
310 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
313class MemoryTester(lsst.utils.tests.MemoryTestCase):
314 pass
317if __name__ == "__main__": 317 ↛ 318line 317 didn't jump to line 318, because the condition on line 317 was never true
318 lsst.utils.tests.init()
319 unittest.main()