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 = 14e-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': 1146.81,
103 'astrometry_final_ndof': 2486,
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 = 12e-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': 1204.72,
141 'astrometry_final_ndof': 2576,
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 self.config.astrometryDoRankUpdate = False
163 self._testJointcalTask(2, relative_error, self.dist_rms_absolute, None, metrics=metrics)
165 def test_jointcalTask_2_visits_constrainedAstrometry_4sigma_outliers(self):
166 """4 sigma outlier rejection means fewer available sources after the
167 fitter converges, resulting in a smaller ndof and chi2.
168 """
169 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry()
170 self.config.outlierRejectSigma = 4
171 metrics['astrometry_final_chi2'] = 849.75
172 metrics['astrometry_final_ndof'] = 2390
174 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, None, metrics=metrics)
176 def test_jointcalTask_2_visits_constrainedAstrometry_astrometryReferenceUncertainty_smaller(self):
177 """Test with a smaller fake reference uncertainty: chi2 will be higher."""
178 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry()
179 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'),
180 'tests/config/astrometryReferenceErr-config.py')
181 self.configfiles.append(test_config)
182 metrics['astrometry_final_chi2'] = 1275.70
183 metrics['astrometry_final_ndof'] = 2062
185 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, None, metrics=metrics)
187 def test_jointcalTask_2_visits_constrainedAstrometry_astrometryReferenceUncertainty_None_fails(self):
188 """The default `None` should fail for the existing refcats that have no position errors."""
189 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry()
190 # This is the default, but we override it in tests/config/config.py,
191 # because none of the existing test refcats have errors. So we have to
192 # re-override it here.
193 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'),
194 'tests/config/astrometryReferenceErr-None-config.py')
195 self.configfiles.append(test_config)
196 with self.assertRaises(lsst.pex.config.FieldValidationError):
197 self._testJointcalTask(2, None, None, None)
199 def setup_jointcalTask_2_visits_constrainedPhotometry(self):
200 """Set default values for the constrainedPhotometry tests, and make
201 the differences between each test and the defaults more obvious.
202 """
203 self.config = lsst.jointcal.jointcal.JointcalConfig()
204 self.config.photometryModel = "constrainedFlux"
205 self.config.doAstrometry = False
206 self.jointcalStatistics.do_astrometry = False
208 # See Readme for an explanation of these empirical values.
209 pa1 = 0.09
210 metrics = {'collected_photometry_refStars': 11570,
211 'selected_photometry_refStars': 2225,
212 'associated_photometry_fittedStars': 2272,
213 'selected_photometry_fittedStars': 2232,
214 'selected_photometry_ccdImages': 12,
215 'photometry_final_chi2': 11649.7,
216 'photometry_final_ndof': 2729
217 }
218 return pa1, metrics
220 def test_jointcalTask_2_visits_constrainedPhotometry_no_astrometry(self):
221 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
222 self.config.writeInitialModel = True # write the initial models
223 # use a temporary directory for debug output, to prevent test collisions
224 with tempfile.TemporaryDirectory() as tempdir:
225 self.config.debugOutputPath = tempdir
227 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
228 filename = os.path.join(tempdir, "initialPhotometryModel.txt")
229 self.assertTrue(os.path.exists(filename), msg=f"Did not find file {filename}")
231 def test_jointcalTask_2_visits_constrainedPhotometry_no_rank_update(self):
232 """Demonstrate that skipping the rank update doesn't substantially affect photometry.
233 """
234 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
235 self.config.photometryDoRankUpdate = False
237 # The constrainedPhotometry model is not purely linear, so a small
238 # change in final chi2 is possible.
239 metrics['photometry_final_chi2'] = 11396.27
240 metrics['photometry_final_ndof'] = 2716
242 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
244 def test_jointcalTask_2_visits_constrainedPhotometry_lineSearch(self):
245 """Activating the line search should only slightly change the chi2.
246 """
247 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
248 self.config.allowLineSearch = True
250 # Activating line search for constrainedPhotometry should result in
251 # nearly the same final fit (the system is somewhat non-linear, so it
252 # may not be exactly the same: check the "Line search scale factor"
253 # lines in the DEBUG log for values that are not ~1 for proof).
254 pa1 = 0.14
255 metrics['photometry_final_chi2'] = 10500.4
256 metrics['photometry_final_ndof'] = 2714
258 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
260 def test_jointcalTask_2_visits_constrainedPhotometry_flagged(self):
261 """Test the use of the FlaggedSourceSelector."""
262 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
263 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'),
264 'tests/config/cfht-flagged-config.py')
265 self.configfiles.append(test_config)
266 # Reduce warnings due to flaggedSourceSelector having fewer sources than astrometrySourceSelector.
267 self.config.minMeasuredStarsPerCcd = 30
268 self.config.minRefStarsPerCcd = 20
270 pa1 = 0.026
271 metrics['selected_photometry_refStars'] = 265
272 metrics['associated_photometry_fittedStars'] = 265
273 metrics['selected_photometry_fittedStars'] = 265
274 metrics['photometry_final_chi2'] = 392.816
275 metrics['photometry_final_ndof'] = 294
277 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
279 def test_jointcalTask_2_visits_constrainedMagnitude_no_astrometry(self):
280 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
281 self.config.photometryModel = "constrainedMagnitude"
283 # The resulting fit should be close to the constrainedFlux model:
284 # there are few CCDs and 2 visits, so there's not a lot of complexity
285 # in this case to distinguish the flux vs. magnitude models.
286 metrics['photometry_final_chi2'] = 9455.06
287 metrics['photometry_final_ndof'] = 2710
289 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
291 def test_jointcalTask_2_visits_constrainedFlux_pedestal(self):
292 """Test that forcing a systematic flux error results in a lower chi2.
293 """
294 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
295 # median fluxErr/flux in ps1 is 0.11, so we have to make this bigger
296 # than that to actually allow more slop in the fit.
297 self.config.photometryErrorPedestal = 0.2
299 # We're allowing more error in the fit, so PA1 may be worse.
300 pa1 = 0.21
301 # Final chi2 is much lower, because all sources contribute more error.
302 metrics['photometry_final_chi2'] = 3214.78
303 # ndof may change; slightly different likelihood contours, and fewer
304 # reference sources rejected.
305 metrics['photometry_final_ndof'] = 3154
307 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
309 def test_jointcalTask_2_visits_constrainedMagnitude_pedestal(self):
310 """Test that forcing a systematic flux error results in a lower chi2.
311 """
312 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
313 self.config.photometryModel = "constrainedMagnitude"
314 # median fluxErr/flux in ps1 is 0.11, so we have to make this bigger
315 # than that to actually allow more slop in the fit.
316 self.config.photometryErrorPedestal = 0.2
318 # We're allowing more error in the fit, so PA1 may be worse.
319 pa1 = 0.19
320 # Final chi2 is much lower, because all sources contribute more error.
321 metrics['photometry_final_chi2'] = 3092.39
322 # ndof may change; slightly different likelihood contours, and fewer
323 # reference sources rejected.
324 metrics['photometry_final_ndof'] = 3129
326 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
329class MemoryTester(lsst.utils.tests.MemoryTestCase):
330 pass
333if __name__ == "__main__": 333 ↛ 334line 333 didn't jump to line 334, because the condition on line 333 was never true
334 lsst.utils.tests.init()
335 unittest.main()