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 = 49e-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 = 11e-3*u.arcsecond
91 pa1 = 0.014
92 metrics = {'collected_astrometry_refStars': 1770,
93 'collected_photometry_refStars': 1770,
94 'selected_astrometry_refStars': 747,
95 'selected_photometry_refStars': 747,
96 'associated_astrometry_fittedStars': 2269,
97 'associated_photometry_fittedStars': 2269,
98 'selected_astrometry_fittedStars': 1408,
99 'selected_photometry_fittedStars': 1408,
100 'selected_astrometry_ccdImages': 12,
101 'selected_photometry_ccdImages': 12,
102 'astrometry_final_chi2': 1609.29,
103 'astrometry_final_ndof': 3332,
104 'photometry_final_chi2': 3632.26,
105 'photometry_final_ndof': 1693
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': 1770,
136 'selected_astrometry_refStars': 747,
137 'associated_astrometry_fittedStars': 2269,
138 'selected_astrometry_fittedStars': 1408,
139 'selected_astrometry_ccdImages': 12,
140 'astrometry_final_chi2': 1714.8,
141 'astrometry_final_ndof': 3434,
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'] = 1288.64
173 metrics['astrometry_final_ndof'] = 3232
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'] = 11522.9
184 metrics['astrometry_final_ndof'] = 3406
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.017
211 metrics = {'collected_photometry_refStars': 1770,
212 'selected_photometry_refStars': 747,
213 'associated_photometry_fittedStars': 2269,
214 'selected_photometry_fittedStars': 1408,
215 'selected_photometry_ccdImages': 12,
216 'photometry_final_chi2': 3292.08,
217 'photometry_final_ndof': 1622
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'] = 3297.34
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 metrics['photometry_final_chi2'] = 3324.2
255 metrics['photometry_final_ndof'] = 1625
257 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
259 def test_jointcalTask_2_visits_constrainedPhotometry_flagged(self):
260 """Test the use of the FlaggedSourceSelector."""
261 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
262 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'),
263 'tests/config/cfht-flagged-config.py')
264 self.configfiles.append(test_config)
265 # Reduce warnings due to flaggedSourceSelector having fewer sources than astrometrySourceSelector.
266 self.config.minMeasuredStarsPerCcd = 30
267 self.config.minRefStarsPerCcd = 20
269 pa1 = 0.026
270 metrics['selected_photometry_refStars'] = 214
271 metrics['associated_photometry_fittedStars'] = 270
272 metrics['selected_photometry_fittedStars'] = 245
273 metrics['photometry_final_chi2'] = 373.141
274 metrics['photometry_final_ndof'] = 254
276 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
278 def test_jointcalTask_2_visits_constrainedMagnitude_no_astrometry(self):
279 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
280 self.config.photometryModel = "constrainedMagnitude"
282 # The resulting fit should be close to the constrainedFlux model:
283 # there are few CCDs and 2 visits, so there's not a lot of complexity
284 # in this case to distinguish the flux vs. magnitude models.
285 metrics['photometry_final_chi2'] = 3332.76
287 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
289 def test_jointcalTask_2_visits_constrainedFlux_pedestal(self):
290 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
291 self.config.photometryErrorPedestal = 0.02
293 # We're allowing more error in the fit, so PA1 may be worse.
294 pa1 = 0.021
295 # Final chi2 is much lower, because all sources contribute more error.
296 metrics['photometry_final_chi2'] = 2246.58
297 # ndof shouldn't change much; slightly different likelihood contours
298 metrics['photometry_final_ndof'] = 1624
300 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
302 def test_jointcalTask_2_visits_constrainedMagnitude_pedestal(self):
303 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry()
304 self.config.photometryModel = "constrainedMagnitude"
305 self.config.photometryErrorPedestal = 0.02
307 # We're allowing more error in the fit, so PA1 may be worse.
308 pa1 = 0.024
309 # Final chi2 is much lower, because all sources contribute more error.
310 metrics['photometry_final_chi2'] = 2243.56
311 # ndof shouldn't change much; slightly different likelihood contours
312 metrics['photometry_final_ndof'] = 1617
314 self._testJointcalTask(2, None, None, pa1, metrics=metrics)
317class MemoryTester(lsst.utils.tests.MemoryTestCase):
318 pass
321if __name__ == "__main__": 321 ↛ 322line 321 didn't jump to line 322, because the condition on line 321 was never true
322 lsst.utils.tests.init()
323 unittest.main()