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 LookupError:
48 raise unittest.SkipTest("testdata_jointcal not setup")
49 try:
50 lsst.utils.getPackageDir('obs_cfht')
51 except LookupError:
52 raise unittest.SkipTest("obs_cfht not setup")
54 def setUp(self):
55 # NOTE: refcat-comparison RMS error is worse now, because the
56 # comparison code is not applying the proper motion data.
57 # See Readme for an explanation of this empirical value.
58 self.dist_rms_absolute = 70e-3*u.arcsecond
60 do_plot = False
62 # center of the cfht validation_data catalog
63 center = lsst.geom.SpherePoint(214.884832, 52.6622199, lsst.geom.degrees)
64 radius = 3*lsst.geom.degrees
66 input_dir = os.path.join(self.data_dir, 'cfht')
67 all_visits = [849375, 850587]
69 self.setUp_base(center, radius,
70 input_dir=input_dir,
71 all_visits=all_visits,
72 do_plot=do_plot,
73 log_level="DEBUG")
75 def test_jointcalTask_2_visits(self):
76 """Test the simple models with two visits and check that some debug
77 output files also get created.
78 """
79 self.config = lsst.jointcal.jointcal.JointcalConfig()
80 self.config.astrometryModel = "simple"
81 self.config.photometryModel = "simpleFlux"
82 self.config.writeInitialModel = True # write the initial models
83 # to test whether we got the expected chi2 contribution files.
84 self.config.writeChi2FilesInitialFinal = True
85 # use a temporary directory for debug output, to prevent test collisions
86 with tempfile.TemporaryDirectory() as tempdir:
87 self.config.debugOutputPath = tempdir
89 # See Readme for an explanation of these empirical values.
90 dist_rms_relative = 16e-3*u.arcsecond
91 pa1 = 0.049
92 metrics = {'collected_astrometry_refStars': 867,
93 'collected_photometry_refStars': 11570,
94 'selected_astrometry_refStars': 332,
95 'selected_photometry_refStars': 2225,
96 'associated_astrometry_fittedStars': 2272,
97 'associated_photometry_fittedStars': 2272,
98 'selected_astrometry_fittedStars': 1229,
99 'selected_photometry_fittedStars': 2232,
100 'selected_astrometry_ccdImages': 12,
101 'selected_photometry_ccdImages': 12,
102 'astrometry_final_chi2': 1159.736,
103 'astrometry_final_ndof': 1872,
104 'photometry_final_chi2': 11624.3,
105 'photometry_final_ndof': 2778
106 }
108 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, pa1, metrics=metrics)
110 # Check for the existence of the chi2 contribution files.
111 expected = ['photometry_initial_chi2-0_r', 'astrometry_initial_chi2-0_r',
112 'photometry_final_chi2-0_r', 'astrometry_final_chi2-0_r']
113 for partial in expected:
114 name = os.path.join(tempdir, partial+'-ref.csv')
115 self.assertTrue(os.path.exists(name), msg="Did not find file %s"%name)
116 name = os.path.join(tempdir, partial+'-meas.csv')
117 self.assertTrue(os.path.exists(name), msg='Did not find file %s'%name)
119 expected = ["initialAstrometryModel.txt", "initialPhotometryModel.txt"]
120 for name in expected:
121 fullpath = os.path.join(tempdir, name)
122 self.assertTrue(os.path.exists(fullpath), msg=f"Did not find file {fullpath}")
124 def setup_jointcalTask_2_visits_constrainedAstrometry(self):
125 """Set default values for the constrainedAstrometry tests, and make
126 the differences between each test and the defaults more obvious.
127 """
128 self.config = lsst.jointcal.jointcal.JointcalConfig()
129 self.config.astrometryModel = "constrained"
130 self.config.doPhotometry = False
131 self.jointcalStatistics.do_photometry = False
133 # See Readme for an explanation of these empirical values.
134 dist_rms_relative = 16e-3*u.arcsecond
135 metrics = {'collected_astrometry_refStars': 867,
136 'selected_astrometry_refStars': 332,
137 'associated_astrometry_fittedStars': 2272,
138 'selected_astrometry_fittedStars': 1229,
139 'selected_astrometry_ccdImages': 12,
140 'astrometry_final_chi2': 1124.0575,
141 'astrometry_final_ndof': 1912,
142 }
144 return dist_rms_relative, metrics
146 def test_jointcalTask_2_visits_constrainedAstrometry_no_photometry(self):
147 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry()
148 self.config.writeInitialModel = True # write the initial models
149 # use a temporary directory for debug output, to prevent test collisions
150 with tempfile.TemporaryDirectory() as tempdir:
151 self.config.debugOutputPath = tempdir
153 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, None, metrics=metrics)
154 filename = os.path.join(tempdir, "initialAstrometryModel.txt")
155 self.assertTrue(os.path.exists(filename), msg=f"Did not find file {filename}")
157 def test_jointcalTask_2_visits_constrainedAstrometry_no_rank_update(self):
158 """Demonstrate that skipping the rank update doesn't substantially affect astrometry.
159 """
160 relative_error, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry()
161 metrics['astrometry_final_chi2'] = 1069.348
162 metrics['astrometry_final_ndof'] = 1900
164 self.config.astrometryDoRankUpdate = False
166 self._testJointcalTask(2, relative_error, self.dist_rms_absolute, None, metrics=metrics)
168 def test_jointcalTask_2_visits_constrainedAstrometry_4sigma_outliers(self):
169 """4 sigma outlier rejection means fewer available sources after the
170 fitter converges, resulting in a smaller ndof and chi2.
171 """
172 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry()
173 self.config.outlierRejectSigma = 4
174 metrics['astrometry_final_chi2'] = 757.027
175 metrics['astrometry_final_ndof'] = 1732
177 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, None, metrics=metrics)
179 def test_jointcalTask_2_visits_constrainedAstrometry_astrometryReferenceUncertainty_smaller(self):
180 """Test with a smaller fake reference uncertainty: chi2 will be higher."""
181 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry()
182 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'),
183 'tests/config/astrometryReferenceErr-config.py')
184 self.configfiles.append(test_config)
185 metrics['astrometry_final_chi2'] = 1275.70
186 metrics['astrometry_final_ndof'] = 2062
188 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, None, metrics=metrics)
190 def test_jointcalTask_2_visits_constrainedAstrometry_astrometryReferenceUncertainty_None_fails(self):
191 """The default `None` should fail for the existing refcats that have no position errors."""
192 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry()
193 # This is the default, but we override it in tests/config/config.py,
194 # because none of the existing test refcats have errors. So we have to
195 # re-override it here.
196 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'),
197 'tests/config/astrometryReferenceErr-None-config.py')
198 self.configfiles.append(test_config)
199 with self.assertRaises(lsst.pex.config.FieldValidationError):
200 self._testJointcalTask(2, None, None, None)
202 def setup_jointcalTask_2_visits_constrainedPhotometry(self):
203 """Set default values for the constrainedPhotometry tests, and make
204 the differences between each test and the defaults more obvious.
205 """
206 self.config = lsst.jointcal.jointcal.JointcalConfig()
207 self.config.photometryModel = "constrainedFlux"
208 self.config.doAstrometry = False
209 self.jointcalStatistics.do_astrometry = False
211 # See Readme for an explanation of these empirical values.
212 pa1 = 0.09
213 metrics = {'collected_photometry_refStars': 11570,
214 'selected_photometry_refStars': 2225,
215 'associated_photometry_fittedStars': 2272,
216 'selected_photometry_fittedStars': 2232,
217 'selected_photometry_ccdImages': 12,
218 'photometry_final_chi2': 11649.7,
219 'photometry_final_ndof': 2729
220 }
221 return pa1, metrics
223 def test_jointcalTask_2_visits_constrainedPhotometry_no_astrometry(self):
224 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
225 self.config.writeInitialModel = True # write the initial models
226 # use a temporary directory for debug output, to prevent test collisions
227 with tempfile.TemporaryDirectory() as tempdir:
228 self.config.debugOutputPath = tempdir
230 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
231 filename = os.path.join(tempdir, "initialPhotometryModel.txt")
232 self.assertTrue(os.path.exists(filename), msg=f"Did not find file {filename}")
234 def test_jointcalTask_2_visits_constrainedPhotometry_no_rank_update(self):
235 """Demonstrate that skipping the rank update doesn't substantially affect photometry.
236 """
237 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
238 self.config.photometryDoRankUpdate = False
240 # The constrainedPhotometry model is not purely linear, so a small
241 # change in final chi2 is possible.
242 metrics['photometry_final_chi2'] = 11396.27
243 metrics['photometry_final_ndof'] = 2716
245 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
247 def test_jointcalTask_2_visits_constrainedPhotometry_lineSearch(self):
248 """Activating the line search should only slightly change the chi2.
249 """
250 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
251 self.config.allowLineSearch = True
253 # Activating line search for constrainedPhotometry should result in
254 # nearly the same final fit (the system is somewhat non-linear, so it
255 # may not be exactly the same: check the "Line search scale factor"
256 # lines in the DEBUG log for values that are not ~1 for proof).
257 pa1 = 0.14
258 metrics['photometry_final_chi2'] = 10500.4
259 metrics['photometry_final_ndof'] = 2714
261 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
263 def test_jointcalTask_2_visits_constrainedPhotometry_flagged(self):
264 """Test the use of the FlaggedSourceSelector."""
265 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
266 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'),
267 'tests/config/cfht-flagged-config.py')
268 self.configfiles.append(test_config)
269 # Reduce warnings due to flaggedSourceSelector having fewer sources than astrometrySourceSelector.
270 self.config.minMeasuredStarsPerCcd = 30
271 self.config.minRefStarsPerCcd = 20
273 pa1 = 0.026
274 metrics['selected_photometry_refStars'] = 265
275 metrics['associated_photometry_fittedStars'] = 265
276 metrics['selected_photometry_fittedStars'] = 265
277 metrics['photometry_final_chi2'] = 392.816
278 metrics['photometry_final_ndof'] = 294
280 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
282 def test_jointcalTask_2_visits_constrainedMagnitude_no_astrometry(self):
283 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
284 self.config.photometryModel = "constrainedMagnitude"
286 # The resulting fit should be close to the constrainedFlux model:
287 # there are few CCDs and 2 visits, so there's not a lot of complexity
288 # in this case to distinguish the flux vs. magnitude models.
289 metrics['photometry_final_chi2'] = 9455.06
290 metrics['photometry_final_ndof'] = 2710
292 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
294 def test_jointcalTask_2_visits_constrainedFlux_pedestal(self):
295 """Test that forcing a systematic flux error results in a lower chi2.
296 """
297 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
298 # median fluxErr/flux in ps1 is 0.11, so we have to make this bigger
299 # than that to actually allow more slop in the fit.
300 self.config.photometryErrorPedestal = 0.2
302 # We're allowing more error in the fit, so PA1 may be worse.
303 pa1 = 0.21
304 # Final chi2 is much lower, because all sources contribute more error.
305 metrics['photometry_final_chi2'] = 3214.78
306 # ndof may change; slightly different likelihood contours, and fewer
307 # reference sources rejected.
308 metrics['photometry_final_ndof'] = 3154
310 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
312 def test_jointcalTask_2_visits_constrainedMagnitude_pedestal(self):
313 """Test that forcing a systematic flux error results in a lower chi2.
314 """
315 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
316 self.config.photometryModel = "constrainedMagnitude"
317 # median fluxErr/flux in ps1 is 0.11, so we have to make this bigger
318 # than that to actually allow more slop in the fit.
319 self.config.photometryErrorPedestal = 0.2
321 # We're allowing more error in the fit, so PA1 may be worse.
322 pa1 = 0.19
323 # Final chi2 is much lower, because all sources contribute more error.
324 metrics['photometry_final_chi2'] = 3092.39
325 # ndof may change; slightly different likelihood contours, and fewer
326 # reference sources rejected.
327 metrics['photometry_final_ndof'] = 3129
329 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
332class MemoryTester(lsst.utils.tests.MemoryTestCase):
333 pass
336if __name__ == "__main__": 336 ↛ 337line 336 didn't jump to line 337, because the condition on line 336 was never true
337 lsst.utils.tests.init()
338 unittest.main()