Coverage for tests/test_processCcd.py : 15%

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#
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 psfShape = exposure.getPsf().computeShape()
119 psfIxx = psfShape.getIxx()
120 psfIyy = psfShape.getIyy()
121 psfIxy = psfShape.getIxy()
123 if i == 0:
124 print("\nMeasured results:")
125 print("background mean = %r, stdDev = %r" % (bgMean, bgStdDev))
126 print("len(icSrc) :", len(icSrc))
127 print("len(src) :", len(src))
129 print("numGoodPix =", numGoodPix)
130 print("image mean = %r, stdDev = %r" % (imMean, imStdDev))
131 print("variance mean = %r, stdDev = %r" % (varMean, varStdDev))
132 print("psf Ixx = %r, Iyy = %r, Ixy = %r" % (psfIxx, psfIyy, psfIxy))
134 self.assertEqual(len(icSrc), 28)
135 self.assertEqual(len(src), 185)
137 expectedPlaces = 7 # Tolerance for numerical comparisons
138 for name, var, val in [
139 ("bgMean", bgMean, 191.48635852060525),
140 ("bgStdDev", bgStdDev, 0.2399466881603354),
141 ("numGoodPix", numGoodPix, 1966820),
142 ("imMean", imMean, 1.1237668985230562),
143 ("imStdDev", imStdDev, 85.81296241298496),
144 ("varMean", varMean, 131.24003624152013),
145 ("varStdDev", varStdDev, 55.98012493452948),
146 ("psfIxx", psfIxx, 2.769679536557131),
147 ("psfIyy", psfIyy, 2.2013649766299324),
148 ("psfIxy", psfIxy, 0.14797939531970852)
149 ]:
150 self.assertAlmostEqual(var, val, places=expectedPlaces, msg=name)
152 else:
153 self.assertEqual(imMean, oldImMean)
154 self.assertEqual(imStdDev, oldImStdDev)
155 self.assertEqual(varMean, oldVarMean)
156 self.assertEqual(varStdDev, oldVarStdDev)
157 self.assertEqual(psfIxx, oldPsfIxx)
158 self.assertEqual(psfIyy, oldPsfIyy)
159 self.assertEqual(psfIxy, oldPsfIxy)
161 oldImMean = imMean
162 oldImStdDev = imStdDev
163 oldVarMean = varMean
164 oldVarStdDev = varStdDev
165 oldPsfIxx = psfIxx
166 oldPsfIyy = psfIyy
167 oldPsfIxy = psfIxy
169 finally:
170 if OutputName is None:
171 shutil.rmtree(outPath)
172 else:
173 print("testProcessCcd.py's output data saved to %r" % (OutputName,))
175 def assertCatalogsEqual(self, catalog1, catalog2, skipCols=()):
176 """Compare two Catalogs for equality.
178 This should only be used in contexts where it's unlikely that the catalogs will be subtly different;
179 instead of comparing all values we simply do a spot check of a few cells.
181 This does not require that either catalog be contiguous (which is why we can't use column access).
182 """
183 self.assertEqual(catalog1.schema, catalog2.schema)
184 self.assertEqual(len(catalog1), len(catalog2))
185 d = catalog1.schema.extract("*")
187 def fixNaN(x):
188 if x != x:
189 return "NaN"
191 for record1, record2 in zip(catalog1, catalog2):
192 for name, item in d.items():
193 if name not in skipCols:
194 self.assertEqual(
195 fixNaN(record1.get(item.key)), fixNaN(record2.get(item.key)),
196 "{} != {} in field {}".format(record1.get(item.key), record2.get(item.key), name)
197 )
199 def assertBackgroundListsEqual(self, bkg1, bkg2):
200 """Compare two BackgroundLists for equality.
202 This should only be used in contexts where it's unlikely that the catalogs will be subtly different;
203 instead of comparing all values we simply do a spot check of a few cells.
205 This does not require that either catalog be contiguous (which is why we can't use column access).
206 """
207 im1 = bkg1.getImage()
208 im2 = bkg2.getImage()
209 self.assertEqual(im1.getBBox(), im2.getBBox())
210 self.assertImagesEqual(im1, im2)
212 def testComponents(self):
213 """Test that we can run the first-level subtasks of ProcessCcdTasks.
215 This tests that we can run these subtasks from the command-line independently (they're all
216 CmdLineTasks) as well as directly from Python (without giving them access to a Butler).
218 Aside from verifying that no exceptions are raised, we simply tests that most persisted results are
219 present and equivalent to both in-memory results.
220 """
221 outPath = tempfile.mkdtemp() if OutputName is None else "{}-Components".format(OutputName)
222 # We'll use an input butler to get data for the tasks we call from Python, but we won't ever give it
223 # to those tasks.
224 inputButler = lsst.daf.persistence.Butler(InputDir)
225 # Construct task instances we can use directly from Python
226 isrTask = IsrTask(
227 config=getObsTestConfig(IsrTask),
228 name="isr2"
229 )
230 # If we ever enable astrometry and photocal in obs_test, we'll need to pass a refObjLoader to these
231 # tasks. To maintain the spirit of these tests, we'd ideally have a LoadReferenceObjectsTask class
232 # that doesn't require a Butler. If we don't, we should construct a butler-based on outside these
233 # task constructors and pass the LoadReferenceObjectsTask instance to the task constructors.
234 charImageTask = CharacterizeImageTask(
235 config=getObsTestConfig(CharacterizeImageTask),
236 name="charImage2"
237 )
238 calibrateTask = CalibrateTask(
239 config=getObsTestConfig(CalibrateTask),
240 name="calibrate2",
241 icSourceSchema=charImageTask.schema
242 )
243 try:
244 dataId = dict(visit=1)
245 dataIdStrList = ["%s=%s" % (key, val) for key, val in dataId.items()]
247 isrResult1 = IsrTask.parseAndRun(
248 args=[InputDir, "--output", outPath, "--clobber-config", "--doraise", "--id"] + dataIdStrList,
249 doReturnResults=True,
250 )
251 # We'll just use the butler to get the original image and calibration frames; it's not clear
252 # extending the test coverage to include that is worth it.
253 dataRef = inputButler.dataRef("raw", dataId=dataId)
254 rawExposure = dataRef.get("raw", immediate=True)
255 camera = dataRef.get("camera")
256 isrData = isrTask.readIsrData(dataRef, rawExposure)
257 isrResult2 = isrTask.run(
258 rawExposure,
259 bias=isrData.bias,
260 linearizer=isrData.linearizer,
261 flat=isrData.flat,
262 defects=isrData.defects,
263 fringes=isrData.fringes,
264 bfKernel=isrData.bfKernel,
265 camera=camera,
266 )
267 self.assertMaskedImagesEqual(
268 isrResult1.parsedCmd.butler.get("postISRCCD", dataId, immediate=True).getMaskedImage(),
269 isrResult1.resultList[0].result.exposure.getMaskedImage()
270 )
271 self.assertMaskedImagesEqual(
272 isrResult2.exposure.getMaskedImage(),
273 isrResult1.resultList[0].result.exposure.getMaskedImage()
274 )
276 icResult1 = CharacterizeImageTask.parseAndRun(
277 args=[InputDir, "--output", outPath, "--clobber-config", "--doraise", "--id"] + dataIdStrList,
278 doReturnResults=True,
279 )
280 icResult2 = charImageTask.run(isrResult2.exposure)
281 self.assertMaskedImagesEqual(
282 icResult1.parsedCmd.butler.get("icExp", dataId, immediate=True).getMaskedImage(),
283 icResult1.resultList[0].result.exposure.getMaskedImage()
284 )
285 self.assertMaskedImagesEqual(
286 icResult2.exposure.getMaskedImage(),
287 icResult1.resultList[0].result.exposure.getMaskedImage()
288 )
289 self.assertCatalogsEqual(
290 icResult1.parsedCmd.butler.get("icSrc", dataId, immediate=True),
291 icResult1.resultList[0].result.sourceCat
292 )
293 self.assertCatalogsEqual(
294 icResult2.sourceCat,
295 icResult1.resultList[0].result.sourceCat,
296 skipCols=("id", "parent") # since we didn't want to pass in an ExposureIdInfo, IDs disagree
297 )
298 self.assertBackgroundListsEqual(
299 icResult1.parsedCmd.butler.get("icExpBackground", dataId, immediate=True),
300 icResult1.resultList[0].result.background
301 )
302 self.assertBackgroundListsEqual(
303 icResult2.background,
304 icResult1.resultList[0].result.background
305 )
307 calResult1 = CalibrateTask.parseAndRun(
308 args=[InputDir, "--output", outPath, "--clobber-config", "--doraise", "--id"] + dataIdStrList,
309 doReturnResults=True,
310 )
311 calResult2 = calibrateTask.run(
312 icResult2.exposure,
313 background=icResult2.background,
314 icSourceCat=icResult2.sourceCat
315 )
316 self.assertMaskedImagesEqual(
317 calResult1.parsedCmd.butler.get("calexp", dataId, immediate=True).getMaskedImage(),
318 calResult1.resultList[0].result.exposure.getMaskedImage()
319 )
320 self.assertMaskedImagesEqual(
321 calResult2.exposure.getMaskedImage(),
322 calResult1.resultList[0].result.exposure.getMaskedImage()
323 )
324 self.assertCatalogsEqual(
325 calResult1.parsedCmd.butler.get("src", dataId, immediate=True),
326 calResult1.resultList[0].result.sourceCat
327 )
328 self.assertCatalogsEqual(
329 calResult2.sourceCat,
330 calResult1.resultList[0].result.sourceCat,
331 skipCols=("id", "parent")
332 )
333 self.assertBackgroundListsEqual(
334 calResult1.parsedCmd.butler.get("calexpBackground", dataId, immediate=True),
335 calResult1.resultList[0].result.background
336 )
337 self.assertBackgroundListsEqual(
338 calResult2.background,
339 calResult1.resultList[0].result.background
340 )
342 finally:
343 if OutputName is None:
344 shutil.rmtree(outPath)
345 else:
346 print("testProcessCcd.py's output data saved to %r" % (OutputName,))
349def setup_module(module):
350 lsst.utils.tests.init()
353class MemoryTestCase(lsst.utils.tests.MemoryTestCase):
354 pass
357if __name__ == "__main__": 357 ↛ 358line 357 didn't jump to line 358, because the condition on line 357 was never true
358 lsst.utils.tests.init()
359 unittest.main()