Coverage for tests/jointcalTestBase.py : 12%

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 os
23import inspect
25import lsst.afw.image.utils
26import lsst.obs.base
27import lsst.geom
29from lsst.jointcal import jointcal, utils
32class JointcalTestBase:
33 """
34 Base class for jointcal tests, to genericize some test running and setup.
36 Derive from this first, then from TestCase.
37 """
39 def setUp_base(self, center, radius,
40 match_radius=0.1*lsst.geom.arcseconds,
41 input_dir="",
42 all_visits=None,
43 other_args=None,
44 do_plot=False,
45 log_level=None):
46 """
47 Call from your child classes's setUp() to get the necessary variables built.
49 Parameters
50 ----------
51 center : lsst.geom.SpherePoint
52 Center of the reference catalog.
53 radius : lsst.geom.Angle
54 Radius from center to load reference catalog objects inside.
55 match_radius : lsst.geom.Angle
56 matching radius when calculating RMS of result.
57 input_dir : str
58 Directory of input butler repository.
59 all_visits : list
60 List of the available visits to generate the parseAndRun arguments.
61 other_args : list
62 Optional other arguments for the butler dataId.
63 do_plot : bool
64 Set to True for a comparison plot and some diagnostic numbers.
65 log_level : str
66 Set to the default log level you want jointcal to produce while the
67 tests are running. See the developer docs about logging for valid
68 levels: https://developer.lsst.io/coding/logging.html
69 """
70 self.center = center
71 self.radius = radius
72 self.jointcalStatistics = utils.JointcalStatistics(match_radius, verbose=True)
73 self.input_dir = input_dir
74 self.all_visits = all_visits
75 if other_args is None:
76 other_args = []
77 self.other_args = other_args
78 self.do_plot = do_plot
79 self.log_level = log_level
80 # Signal/Noise (flux/fluxErr) for sources to be included in the RMS cross-match.
81 # 100 is a balance between good centroids and enough sources.
82 self.flux_limit = 100
84 # Individual tests may want to tweak the config that is passed to parseAndRun().
85 self.config = None
86 self.configfiles = []
88 # Append `msg` arguments to assert failures.
89 self.longMessage = True
91 # Ensure that the filter list is reset for each test so that we avoid
92 # confusion or contamination from other instruments.
93 lsst.obs.base.FilterDefinitionCollection.reset()
95 def tearDown(self):
96 if getattr(self, 'reference', None) is not None:
97 del self.reference
98 if getattr(self, 'oldWcsList', None) is not None:
99 del self.oldWcsList
100 if getattr(self, 'jointcalTask', None) is not None:
101 del self.jointcalTask
102 if getattr(self, 'jointcalStatistics', None) is not None:
103 del self.jointcalStatistics
104 if getattr(self, 'config', None) is not None:
105 del self.config
107 def _testJointcalTask(self, nCatalogs, dist_rms_relative, dist_rms_absolute, pa1,
108 metrics=None):
109 """
110 Test parseAndRun for jointcal on nCatalogs.
112 Checks relative and absolute astrometric error (arcsec) and photometric
113 repeatability (PA1 from the SRD).
115 Parameters
116 ----------
117 nCatalogs : int
118 Number of catalogs to run jointcal on. Used to construct the "id"
119 field for parseAndRun.
120 dist_rms_relative : astropy.Quantity
121 Minimum relative astrometric rms post-jointcal to pass the test.
122 dist_rms_absolute : astropy.Quantity
123 Minimum absolute astrometric rms post-jointcal to pass the test.
124 pa1 : float
125 Minimum PA1 (from Table 14 of the Science Requirements Document:
126 https://ls.st/LPM-17) post-jointcal to pass the test.
127 metrics : dict, optional
128 Dictionary of 'metricName': value to test jointcal's result.metrics
129 against.
131 Returns
132 -------
133 list of lsst.daf.persistence.ButlerDataRef
134 The dataRefs that were processed.
135 """
137 # the calling method is one step back on the stack: use it to specify the output repo.
138 caller = inspect.stack()[1].function
140 result = self._runJointcalTask(nCatalogs, caller, metrics=metrics)
142 data_refs = result.resultList[0].result.dataRefs
143 oldWcsList = result.resultList[0].result.oldWcsList
145 # extract a reference catalog to compute statistics against
146 refObjLoader = result.resultList[0].result.photometryRefObjLoader
147 # Not all tests do astrometry, so might not have the above defined.
148 if refObjLoader is None:
149 refObjLoader = result.resultList[0].result.astrometryRefObjLoader
150 defaultFilter = result.resultList[0].result.defaultFilter
151 refCat = refObjLoader.loadSkyCircle(self.center, self.radius, defaultFilter).refCat
153 rms_result = self.jointcalStatistics.compute_rms(data_refs, refCat)
154 # Make plots before testing, if requested, so we still get plots if tests fail.
155 if self.do_plot:
156 self._plotJointcalTask(data_refs, oldWcsList, caller)
158 if dist_rms_relative is not None and dist_rms_absolute is not None:
159 self.assertLess(rms_result.dist_relative, dist_rms_relative)
160 self.assertLess(rms_result.dist_absolute, dist_rms_absolute)
161 if pa1 is not None:
162 self.assertLess(rms_result.pa1, pa1)
164 return data_refs
166 def _runJointcalTask(self, nCatalogs, caller, metrics=None):
167 """
168 Run jointcalTask on nCatalogs, with the most basic tests.
169 Tests for non-empty result list, and that the basic metrics are correct.
171 Parameters
172 ----------
173 nCatalogs : int
174 Number of catalogs to test on.
175 caller : str
176 Name of the calling function (to determine output directory).
177 metrics : dict, optional
178 Dictionary of 'metricName': value to test jointcal's result.metrics
179 against.
181 Returns
182 -------
183 pipe.base.Struct
184 The structure returned by jointcalTask.run()
185 """
186 visits = '^'.join(str(v) for v in self.all_visits[:nCatalogs])
187 output_dir = os.path.join('.test', self.__class__.__name__, caller)
188 if self.log_level is not None:
189 self.other_args.extend(['--loglevel', 'jointcal=%s'%self.log_level])
191 # Place default configfile first so that specific subclass configfiles are applied after
192 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'), 'tests/config/config.py')
193 self.configfiles = [test_config] + self.configfiles
195 args = [self.input_dir, '--output', output_dir,
196 '--clobber-versions', '--clobber-config',
197 '--doraise', '--configfile', *self.configfiles,
198 '--id', 'visit=%s'%visits]
199 args.extend(self.other_args)
200 result = jointcal.JointcalTask.parseAndRun(args=args, doReturnResults=True, config=self.config)
201 self.assertNotEqual(result.resultList, [], 'resultList should not be empty')
202 self.assertEqual(result.resultList[0].exitStatus, 0)
203 job = result.resultList[0].result.job
204 self._test_metrics(job.measurements, metrics)
206 return result
208 def _plotJointcalTask(self, data_refs, oldWcsList, caller):
209 """
210 Plot the results of a jointcal run.
212 Parameters
213 ----------
214 data_refs : list of lsst.daf.persistence.ButlerDataRef
215 The dataRefs that were processed.
216 oldWcsList : list of lsst.afw.image.Wcs
217 The original WCS from each dataRef.
218 caller : str
219 Name of the calling function (to determine output directory).
220 """
221 plot_dir = os.path.join('.test', self.__class__.__name__, 'plots')
222 if not os.path.isdir(plot_dir):
223 os.mkdir(plot_dir)
224 self.jointcalStatistics.make_plots(data_refs, oldWcsList, name=caller, outdir=plot_dir)
225 print("Plots saved to: {}".format(plot_dir))
227 def _test_metrics(self, result, expect):
228 """Test a dictionary of "metrics" against those returned by jointcal.py
230 Parameters
231 ----------
232 result : dict
233 Result metric dictionary from jointcal.py
234 expect : dict
235 Expected metric dictionary; set a value to None to not test it.
236 """
237 for key in result:
238 if expect[key.metric] is not None:
239 value = result[key].quantity.value
240 if isinstance(value, float):
241 self.assertFloatsAlmostEqual(value, expect[key.metric], msg=key.metric, rtol=1e-5)
242 else:
243 self.assertEqual(value, expect[key.metric], msg=key.metric)