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