Coverage for tests/test_processCcd.py: 16%
Shortcuts 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
Shortcuts 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#
2# LSST Data Management System
3# Copyright 2008-2016 AURA/LSST.
4#
5# This product includes software developed by the
6# LSST Project (http://www.lsst.org/).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the LSST License Statement and
19# the GNU General Public License along with this program. If not,
20# see <https://www.lsstcorp.org/LegalNotices/>.
21#
22"""Test ProcessCcdTask and its immediate subtasks.
24Run the task on one obs_test image and perform various checks on the results
25"""
26import os
27import shutil
28import tempfile
29import unittest
31import numpy as np
33import lsst.utils
34import lsst.geom as geom
35import lsst.utils.tests
36from lsst.ip.isr import IsrTask # we assume obs_test uses base IsrTask here; may change in future.
37from lsst.pipe.tasks.characterizeImage import CharacterizeImageTask
38from lsst.pipe.tasks.calibrate import CalibrateTask
39from lsst.pipe.tasks.processCcd import ProcessCcdTask
41obsTestDir = lsst.utils.getPackageDir('obs_test')
42InputDir = os.path.join(obsTestDir, "data", "input")
44OutputName = None # specify a name (as a string) to save the output repository
47def getObsTestConfig(TaskClass):
48 """Helper function to get a command-line task config customized by obs_test.
50 This duplicates the config override code in pipe_base's ArgumentParser, but
51 essentially in order to test it.
52 """
53 config = TaskClass.ConfigClass()
54 filename = os.path.join(obsTestDir, "config", TaskClass._DefaultName + ".py")
55 if os.path.exists(filename):
56 config.load(filename)
57 return config
60class ProcessCcdTestCase(lsst.utils.tests.TestCase):
62 def testProcessCcd(self):
63 """test ProcessCcdTask via parseAndRun (simulating the command line)
65 This is intended as a sanity check of the task, not a detailed test of its sub-tasks. As such
66 comparisons are intentionally loose, so as to allow evolution of the sub-tasks over time
67 without breaking this test.
68 """
69 outPath = tempfile.mkdtemp() if OutputName is None else "{}-ProcessCcd".format(OutputName)
70 try:
71 dataId = dict(visit=1)
72 dataIdStrList = ["%s=%s" % (key, val) for key, val in dataId.items()]
73 fullResult = ProcessCcdTask.parseAndRun(
74 args=[InputDir, "--output", outPath, "--clobber-config", "--doraise",
75 "-c", "charImage.doWriteExposure=True", "--id"] + dataIdStrList,
76 doReturnResults=True
77 )
78 butler = fullResult.parsedCmd.butler
79 self.assertEqual(len(fullResult.resultList), 1)
80 runResult = fullResult.resultList[0]
81 fullDataId = runResult.dataRef.dataId
82 self.assertEqual(len(fullDataId), 2)
83 self.assertEqual(fullDataId["visit"], dataId["visit"])
84 self.assertEqual(fullDataId["filter"], "g")
85 result = runResult.result
87 icExpBackground = butler.get("icExpBackground", dataId, immediate=True)
88 bg0Arr = icExpBackground.getImage().getArray()
89 bgMean = bg0Arr.mean(dtype=np.float64)
90 bgStdDev = bg0Arr.std(dtype=np.float64)
92 icSrc = butler.get("icSrc", dataId)
93 src = butler.get("src", dataId)
95 # the following makes pyflakes linter happy and the code more robust
96 oldImMean = None
97 oldImStdDev = None
98 oldVarMean = None
99 oldVarStdDev = None
100 oldPsfIxx = None
101 oldPsfIyy = None
102 oldPsfIxy = None
104 for i, exposure in enumerate((butler.get("calexp", dataId), result.exposure)):
105 self.assertEqual(exposure.getBBox(),
106 geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(1018, 2000)))
107 maskedImage = exposure.getMaskedImage()
108 maskArr = maskedImage.getMask().getArray()
109 numGoodPix = np.sum(maskArr == 0)
111 imageArr = maskedImage.getImage().getArray()
112 imMean = imageArr.mean(dtype=np.float64)
113 imStdDev = imageArr.std(dtype=np.float64)
114 varArr = maskedImage.getVariance().getArray()
115 varMean = varArr.mean(dtype=np.float64)
116 varStdDev = varArr.std(dtype=np.float64)
118 summary = exposure.getInfo().getSummaryStats()
120 psf = exposure.getPsf()
121 psfAvgPos = psf.getAveragePosition()
122 psfShape = psf.computeShape(psfAvgPos)
123 psfIxx = psfShape.getIxx()
124 psfIyy = psfShape.getIyy()
125 psfIxy = psfShape.getIxy()
127 if i == 0:
128 print("\nMeasured results:")
129 print("background mean = %r, stdDev = %r" % (bgMean, bgStdDev))
130 print("len(icSrc) :", len(icSrc))
131 print("len(src) :", len(src))
133 print("numGoodPix =", numGoodPix)
134 print("image mean = %r, stdDev = %r" % (imMean, imStdDev))
135 print("variance mean = %r, stdDev = %r" % (varMean, varStdDev))
136 print("psf Ixx = %r, Iyy = %r, Ixy = %r" % (psfIxx, psfIyy, psfIxy))
138 self.assertEqual(len(icSrc), 28)
139 self.assertEqual(len(src), 184 + 100) # 184 real sources plus 100 sky sources
141 # NOTE: These values are purely empirical, and need to be
142 # updated to reflect major algorithmic changes.
143 # If this test fails after an algorithmic change due to
144 # small numeric changes here, check on slack at
145 # #dm-science-pipelines as to whether the changes are
146 # reasonable, and then replace the failing values by
147 # running the test to determine the updated values.
148 expectedPlaces = 7 # Tolerance for numerical comparisons
149 for name, var, val in [
150 ("bgMean", bgMean, 191.48623786891795),
151 ("bgStdDev", bgStdDev, 0.23994185672586282),
152 ("numGoodPix", numGoodPix, 1966606),
153 ("imMean", imMean, 1.1242456954648634),
154 ("imStdDev", imStdDev, 85.8129750182329),
155 ("varMean", varMean, 131.24003624152013),
156 ("varStdDev", varStdDev, 55.98012493452948),
157 ("psfIxx", psfIxx, 2.843329671276296),
158 ("psfIyy", psfIyy, 2.2249941554078156),
159 ("psfIxy", psfIxy, 0.16073332780683286),
160 ("summary_psfSigma", summary.psfSigma, 1.581520120798809),
161 ("summary_psfIxx", summary.psfIxx, 2.8524883317493583),
162 ("summary_psfIyy", summary.psfIyy, 2.2028393759764615),
163 ("summary_psfIxy", summary.psfIxy, 0.16595993509518148),
164 ("summary_psfArea", summary.psfArea, 38.63468352371086),
165 ("summary_ra", summary.ra, 78.85551507080474),
166 ("summary_decl", summary.decl, -9.800258687592303),
167 ("summary_zenithDistance", float('%.6f' % (summary.zenithDistance)), 42.361001),
168 ("summary_zeroPoint", summary.zeroPoint, 30.940228147639207),
169 ("summary_skyBg", summary.skyBg, 191.37726892903447),
170 ("summary_skyNoise", summary.skyNoise, 12.512272992606531),
171 ("summary_meanVar", summary.meanVar, 130.61335199119068)
172 ]:
173 self.assertAlmostEqual(var, val, places=expectedPlaces, msg=name)
175 else:
176 self.assertEqual(imMean, oldImMean)
177 self.assertEqual(imStdDev, oldImStdDev)
178 self.assertEqual(varMean, oldVarMean)
179 self.assertEqual(varStdDev, oldVarStdDev)
180 self.assertEqual(psfIxx, oldPsfIxx)
181 self.assertEqual(psfIyy, oldPsfIyy)
182 self.assertEqual(psfIxy, oldPsfIxy)
184 oldImMean = imMean
185 oldImStdDev = imStdDev
186 oldVarMean = varMean
187 oldVarStdDev = varStdDev
188 oldPsfIxx = psfIxx
189 oldPsfIyy = psfIyy
190 oldPsfIxy = psfIxy
192 finally:
193 if OutputName is None:
194 shutil.rmtree(outPath)
195 else:
196 print("testProcessCcd.py's output data saved to %r" % (OutputName,))
198 def assertCatalogsEqual(self, catalog1, catalog2, skipCols=()):
199 """Compare two Catalogs for equality.
201 This should only be used in contexts where it's unlikely that the catalogs will be subtly different;
202 instead of comparing all values we simply do a spot check of a few cells.
204 This does not require that either catalog be contiguous (which is why we can't use column access).
205 """
206 self.assertEqual(catalog1.schema, catalog2.schema)
207 self.assertEqual(len(catalog1), len(catalog2))
208 d = catalog1.schema.extract("*")
210 def fixNaN(x):
211 if x != x:
212 return "NaN"
214 for record1, record2 in zip(catalog1, catalog2):
215 for name, item in d.items():
216 if name not in skipCols:
217 self.assertEqual(
218 fixNaN(record1.get(item.key)), fixNaN(record2.get(item.key)),
219 "{} != {} in field {}".format(record1.get(item.key), record2.get(item.key), name)
220 )
222 def assertBackgroundListsEqual(self, bkg1, bkg2):
223 """Compare two BackgroundLists for equality.
225 This should only be used in contexts where it's unlikely that the catalogs will be subtly different;
226 instead of comparing all values we simply do a spot check of a few cells.
228 This does not require that either catalog be contiguous (which is why we can't use column access).
229 """
230 im1 = bkg1.getImage()
231 im2 = bkg2.getImage()
232 self.assertEqual(im1.getBBox(), im2.getBBox())
233 self.assertImagesEqual(im1, im2)
235 def testComponents(self):
236 """Test that we can run the first-level subtasks of ProcessCcdTasks.
238 This tests that we can run these subtasks from the command-line independently (they're all
239 CmdLineTasks) as well as directly from Python (without giving them access to a Butler).
241 Aside from verifying that no exceptions are raised, we simply tests that most persisted results are
242 present and equivalent to both in-memory results.
243 """
244 outPath = tempfile.mkdtemp() if OutputName is None else "{}-Components".format(OutputName)
245 # We'll use an input butler to get data for the tasks we call from Python, but we won't ever give it
246 # to those tasks.
247 inputButler = lsst.daf.persistence.Butler(InputDir)
248 # Construct task instances we can use directly from Python
249 isrTask = IsrTask(
250 config=getObsTestConfig(IsrTask),
251 name="isr2"
252 )
253 # If we ever enable astrometry and photocal in obs_test, we'll need to pass a refObjLoader to these
254 # tasks. To maintain the spirit of these tests, we'd ideally have a LoadReferenceObjectsTask class
255 # that doesn't require a Butler. If we don't, we should construct a butler-based on outside these
256 # task constructors and pass the LoadReferenceObjectsTask instance to the task constructors.
257 charImageTask = CharacterizeImageTask(
258 config=getObsTestConfig(CharacterizeImageTask),
259 name="charImage2"
260 )
261 calibrateTask = CalibrateTask(
262 config=getObsTestConfig(CalibrateTask),
263 name="calibrate2",
264 icSourceSchema=charImageTask.schema
265 )
266 try:
267 dataId = dict(visit=1)
268 dataIdStrList = ["%s=%s" % (key, val) for key, val in dataId.items()]
270 isrResult1 = IsrTask.parseAndRun(
271 args=[InputDir, "--output", outPath, "--clobber-config", "--doraise", "--id"] + dataIdStrList,
272 doReturnResults=True,
273 )
274 # We'll just use the butler to get the original image and calibration frames; it's not clear
275 # extending the test coverage to include that is worth it.
276 dataRef = inputButler.dataRef("raw", dataId=dataId)
277 rawExposure = dataRef.get("raw", immediate=True)
278 camera = dataRef.get("camera")
279 isrData = isrTask.readIsrData(dataRef, rawExposure)
280 exposureIdInfo = inputButler.get("expIdInfo", dataId=dataId)
281 isrResult2 = isrTask.run(
282 rawExposure,
283 bias=isrData.bias,
284 linearizer=isrData.linearizer,
285 flat=isrData.flat,
286 defects=isrData.defects,
287 fringes=isrData.fringes,
288 bfKernel=isrData.bfKernel,
289 camera=camera,
290 )
291 self.assertMaskedImagesEqual(
292 isrResult1.parsedCmd.butler.get("postISRCCD", dataId, immediate=True).getMaskedImage(),
293 isrResult1.resultList[0].result.exposure.getMaskedImage()
294 )
295 self.assertMaskedImagesEqual(
296 isrResult2.exposure.getMaskedImage(),
297 isrResult1.resultList[0].result.exposure.getMaskedImage()
298 )
300 icResult1 = CharacterizeImageTask.parseAndRun(
301 args=[InputDir, "--output", outPath, "--clobber-config", "--doraise", "--id"] + dataIdStrList,
302 doReturnResults=True,
303 )
304 icResult2 = charImageTask.run(isrResult2.exposure, exposureIdInfo=exposureIdInfo)
305 self.assertMaskedImagesEqual(
306 icResult1.parsedCmd.butler.get("icExp", dataId, immediate=True).getMaskedImage(),
307 icResult1.resultList[0].result.exposure.getMaskedImage()
308 )
309 self.assertMaskedImagesEqual(
310 icResult2.exposure.getMaskedImage(),
311 icResult1.resultList[0].result.exposure.getMaskedImage()
312 )
313 self.assertCatalogsEqual(
314 icResult1.parsedCmd.butler.get("icSrc", dataId, immediate=True),
315 icResult1.resultList[0].result.sourceCat
316 )
317 self.assertCatalogsEqual(
318 icResult2.sourceCat,
319 icResult1.resultList[0].result.sourceCat,
320 )
321 self.assertBackgroundListsEqual(
322 icResult1.parsedCmd.butler.get("icExpBackground", dataId, immediate=True),
323 icResult1.resultList[0].result.background
324 )
325 self.assertBackgroundListsEqual(
326 icResult2.background,
327 icResult1.resultList[0].result.background
328 )
330 calResult1 = CalibrateTask.parseAndRun(
331 args=[InputDir, "--output", outPath, "--clobber-config", "--doraise", "--id"] + dataIdStrList,
332 doReturnResults=True,
333 )
334 calResult2 = calibrateTask.run(
335 icResult2.exposure,
336 background=icResult2.background,
337 icSourceCat=icResult2.sourceCat,
338 exposureIdInfo=exposureIdInfo,
339 )
340 self.assertMaskedImagesEqual(
341 calResult1.parsedCmd.butler.get("calexp", dataId, immediate=True).getMaskedImage(),
342 calResult1.resultList[0].result.exposure.getMaskedImage()
343 )
344 self.assertMaskedImagesEqual(
345 calResult2.exposure.getMaskedImage(),
346 calResult1.resultList[0].result.exposure.getMaskedImage()
347 )
348 self.assertCatalogsEqual(
349 calResult1.parsedCmd.butler.get("src", dataId, immediate=True),
350 calResult1.resultList[0].result.sourceCat
351 )
352 self.assertCatalogsEqual(
353 calResult2.sourceCat,
354 calResult1.resultList[0].result.sourceCat,
355 skipCols=("id", "parent")
356 )
357 self.assertBackgroundListsEqual(
358 calResult1.parsedCmd.butler.get("calexpBackground", dataId, immediate=True),
359 calResult1.resultList[0].result.background
360 )
361 self.assertBackgroundListsEqual(
362 calResult2.background,
363 calResult1.resultList[0].result.background
364 )
366 finally:
367 if OutputName is None:
368 shutil.rmtree(outPath)
369 else:
370 print("testProcessCcd.py's output data saved to %r" % (OutputName,))
373def setup_module(module):
374 lsst.utils.tests.init()
377class MemoryTestCase(lsst.utils.tests.MemoryTestCase):
378 pass
381if __name__ == "__main__": 381 ↛ 382line 381 didn't jump to line 382, because the condition on line 381 was never true
382 lsst.utils.tests.init()
383 unittest.main()