Coverage for tests / test_methods.py: 8%
226 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-30 01:50 -0700
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-30 01:50 -0700
1#
2# LSST Data Management System
3# Copyright 2008, 2009, 2010 LSST Corporation.
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 <http://www.lsstcorp.org/LegalNotices/>.
21#
22import unittest
24import numpy as np
26import lsst.utils.tests
27import lsst.daf.base as dafBase
28import lsst.geom
29import lsst.afw.geom as afwGeom
30import lsst.afw.image as afwImage
31from lsst.afw.image.testUtils import imagesDiffer
32from lsst.afw.geom.utils import _compareWcsOverBBox
35class TestTestUtils(lsst.utils.tests.TestCase):
36 """Test test methods added to lsst.utils.tests.TestCase
37 """
38 def testAssertWcsAlmostEqualOverBBox(self):
39 """Test assertWcsAlmostEqualOverBBox and wcsAlmostEqualOverBBox"""
40 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
41 lsst.geom.Extent2I(3001, 3001))
42 ctrPix = lsst.geom.Point2I(1500, 1500)
43 metadata = dafBase.PropertySet()
44 metadata.set("RADESYS", "FK5")
45 metadata.set("EQUINOX", 2000.0)
46 metadata.set("CTYPE1", "RA---TAN")
47 metadata.set("CTYPE2", "DEC--TAN")
48 metadata.set("CUNIT1", "deg")
49 metadata.set("CUNIT2", "deg")
50 metadata.set("CRVAL1", 215.5)
51 metadata.set("CRVAL2", 53.0)
52 metadata.set("CRPIX1", ctrPix[0] + 1)
53 metadata.set("CRPIX2", ctrPix[1] + 1)
54 metadata.set("CD1_1", 5.1e-05)
55 metadata.set("CD1_2", 0.0)
56 metadata.set("CD2_2", -5.1e-05)
57 metadata.set("CD2_1", 0.0)
58 wcs0 = lsst.afw.geom.makeSkyWcs(metadata, strip=False)
59 metadata.set("CRVAL2", 53.000001) # tweak CRVAL2 for wcs1
60 wcs1 = lsst.afw.geom.makeSkyWcs(metadata)
62 self.assertWcsAlmostEqualOverBBox(wcs0, wcs0, bbox,
63 maxDiffSky=0*lsst.geom.arcseconds, maxDiffPix=0)
64 self.assertTrue(afwGeom.wcsAlmostEqualOverBBox(wcs0, wcs0, bbox,
65 maxDiffSky=0*lsst.geom.arcseconds, maxDiffPix=0))
67 self.assertWcsAlmostEqualOverBBox(wcs0, wcs1, bbox,
68 maxDiffSky=0.04*lsst.geom.arcseconds, maxDiffPix=0.02)
69 self.assertTrue(afwGeom.wcsAlmostEqualOverBBox(wcs0, wcs1, bbox,
70 maxDiffSky=0.04*lsst.geom.arcseconds, maxDiffPix=0.02))
72 with self.assertRaises(AssertionError):
73 self.assertWcsAlmostEqualOverBBox(wcs0, wcs1, bbox,
74 maxDiffSky=0.001*lsst.geom.arcseconds, maxDiffPix=0.02)
75 self.assertFalse(afwGeom.wcsAlmostEqualOverBBox(wcs0, wcs1, bbox,
76 maxDiffSky=0.001*lsst.geom.arcseconds,
77 maxDiffPix=0.02))
79 with self.assertRaises(AssertionError):
80 self.assertWcsAlmostEqualOverBBox(wcs0, wcs1, bbox,
81 maxDiffSky=0.04*lsst.geom.arcseconds, maxDiffPix=0.001)
82 self.assertFalse(afwGeom.wcsAlmostEqualOverBBox(wcs0, wcs1, bbox,
83 maxDiffSky=0.04*lsst.geom.arcseconds,
84 maxDiffPix=0.001))
86 # check that doShortCircuit works in the private implementation
87 errStr1 = _compareWcsOverBBox(wcs0, wcs1, bbox,
88 maxDiffSky=0.001*lsst.geom.arcseconds, maxDiffPix=0.001,
89 doShortCircuit=False)
90 errStr2 = _compareWcsOverBBox(wcs0, wcs1, bbox,
91 maxDiffSky=0.001*lsst.geom.arcseconds, maxDiffPix=0.001,
92 doShortCircuit=True)
93 self.assertNotEqual(errStr1, errStr2)
95 def checkMaskedImage(self, mi):
96 """Run assertImage-like function tests on a masked image
98 Compare the masked image to itself, then alter copies and check that the altered copy
99 is or is not nearly equal the original, depending on the amount of change, rtol and atol
100 """
101 epsilon = 1e-5 # margin to avoid roundoff error
103 mi0 = mi.Factory(mi, True) # deep copy
104 mi1 = mi.Factory(mi, True)
106 # a masked image should be exactly equal to itself
107 self.assertMaskedImagesEqual(mi0, mi1)
108 self.assertMaskedImagesEqual(mi1, mi0)
109 self.assertMaskedImagesAlmostEqual(mi0, mi1, atol=0, rtol=0)
110 self.assertMaskedImagesAlmostEqual(mi0, mi1, atol=0, rtol=0)
111 self.assertMaskedImagesAlmostEqual(
112 (mi0.image.array, mi0.mask.array, mi0.variance.array), mi1, atol=0, rtol=0)
113 self.assertMaskedImagesAlmostEqual(
114 mi0, (mi1.image.array, mi1.mask.array, mi1.variance.array), atol=0, rtol=0)
115 self.assertMaskedImagesAlmostEqual(
116 (mi0.image.array, mi0.mask.array, mi0.variance.array),
117 (mi1.image.array, mi1.mask.array, mi1.variance.array), atol=0, rtol=0)
118 for getName in ("getImage", "getVariance"):
119 plane0 = getattr(mi0, getName)()
120 plane1 = getattr(mi1, getName)()
121 self.assertImagesEqual(plane0, plane1)
122 self.assertImagesEqual(plane1, plane0)
123 self.assertImagesAlmostEqual(plane0, plane1, atol=0, rtol=0)
124 self.assertImagesAlmostEqual(plane1, plane0, atol=0, rtol=0)
125 self.assertImagesAlmostEqual(
126 plane0.getArray(), plane1, atol=0, rtol=0)
127 self.assertImagesAlmostEqual(
128 plane0, plane1.getArray(), atol=0, rtol=0)
129 self.assertImagesAlmostEqual(
130 plane0.getArray(), plane1.getArray(), atol=0, rtol=0)
131 self.assertMasksEqual(plane0, plane1)
132 self.assertMasksEqual(plane1, plane0)
133 self.assertMasksEqual(plane0.getArray(), plane1)
134 self.assertMasksEqual(plane0, plane1.getArray())
135 self.assertMasksEqual(plane0.getArray(), plane1.getArray())
136 self.assertMasksEqual(mi0.getMask(), mi1.getMask())
137 self.assertMasksEqual(mi1.getMask(), mi0.getMask())
139 # alter image and variance planes and check the results
140 for getName in ("getImage", "getVariance"):
141 isFloat = getattr(mi, getName)().getArray().dtype.kind == "f"
142 if isFloat:
143 for errVal in (np.nan, np.inf, -np.inf):
144 mi0 = mi.Factory(mi, True)
145 mi1 = mi.Factory(mi, True)
146 plane0 = getattr(mi0, getName)()
147 plane1 = getattr(mi1, getName)()
148 plane1[2, 2] = errVal
149 with self.assertRaises(Exception):
150 self.assertImagesAlmostEqual(plane0, plane1)
151 with self.assertRaises(Exception):
152 self.assertImagesAlmostEqual(plane0.getArray(), plane1)
153 with self.assertRaises(Exception):
154 self.assertImagesAlmostEqual(plane1, plane0)
155 with self.assertRaises(Exception):
156 self.assertMaskedImagesAlmostEqual(mi0, mi1)
157 with self.assertRaises(Exception):
158 self.assertMaskedImagesAlmostEqual(
159 mi0, (mi1.image.array, mi1.mask.array, mi1.variance.array))
160 with self.assertRaises(Exception):
161 self.assertMaskedImagesAlmostEqual(mi1, mi0)
163 skipMask = mi.getMask().Factory(mi.getMask(), True)
164 skipMaskArr = skipMask.getArray()
165 skipMaskArr[:] = 0
166 skipMaskArr[2, 2] = 1
167 self.assertImagesAlmostEqual(
168 plane0, plane1, skipMask=skipMaskArr, atol=0, rtol=0)
169 self.assertImagesAlmostEqual(
170 plane0, plane1, skipMask=skipMask, atol=0, rtol=0)
171 self.assertMaskedImagesAlmostEqual(
172 mi0, mi1, skipMask=skipMaskArr, atol=0, rtol=0)
173 self.assertMaskedImagesAlmostEqual(
174 mi0, mi1, skipMask=skipMask, atol=0, rtol=0)
176 for dval in (0.001, 0.03):
177 mi0 = mi.Factory(mi, True)
178 mi1 = mi.Factory(mi, True)
179 plane0 = getattr(mi0, getName)()
180 plane1 = getattr(mi1, getName)()
181 plane1[2, 2] += dval
182 val1 = plane1[2, 2, afwImage.LOCAL]
183 self.assertImagesAlmostEqual(
184 plane0, plane1, rtol=0, atol=dval + epsilon)
185 self.assertImagesAlmostEqual(
186 plane0, plane1, rtol=dval/val1 + epsilon, atol=0)
187 self.assertMaskedImagesAlmostEqual(
188 mi0, mi1, rtol=0, atol=dval + epsilon)
189 self.assertMaskedImagesAlmostEqual(
190 mi1, mi0, rtol=0, atol=dval + epsilon)
191 with self.assertRaises(Exception):
192 self.assertImagesAlmostEqual(
193 plane0, plane1, rtol=0, atol=dval - epsilon)
194 with self.assertRaises(Exception):
195 self.assertImagesAlmostEqual(
196 plane0, plane1, rtol=dval/val1 - epsilon, atol=0)
197 with self.assertRaises(Exception):
198 self.assertMaskedImagesAlmostEqual(
199 mi0, mi1, rtol=0, atol=dval - epsilon)
200 with self.assertRaises(Exception):
201 self.assertMaskedImagesAlmostEqual(
202 mi0, mi1, rtol=dval/val1 - epsilon, atol=0)
203 else:
204 # plane is an integer of some type
205 for dval in (1, 3):
206 mi0 = mi.Factory(mi, True)
207 mi1 = mi.Factory(mi, True)
208 plane0 = getattr(mi0, getName)()
209 plane1 = getattr(mi1, getName)()
210 plane1[2, 2] += dval
211 val1 = plane1[2, 2, afwImage.LOCAL]
212 # int value and test is <= so epsilon not required for atol
213 # but rtol is a fraction, so epsilon is still safest for
214 # the rtol test
215 self.assertImagesAlmostEqual(
216 plane0, plane1, rtol=0, atol=dval)
217 self.assertImagesAlmostEqual(
218 plane0, plane1, rtol=dval/val1 + epsilon, atol=0)
219 with self.assertRaises(Exception):
220 self.assertImagesAlmostEqual(
221 plane0, plane1, rtol=0, atol=dval - epsilon)
222 with self.assertRaises(Exception):
223 self.assertImagesAlmostEqual(
224 plane0, plane1, rtol=dval/val1 - epsilon, atol=0)
226 # alter mask and check the results
227 mi0 = mi.Factory(mi, True)
228 mi1 = mi.Factory(mi, True)
229 mask0 = mi0.getMask()
230 mask1 = mi1.getMask()
231 for dval in (1, 3):
232 # getArray avoids "unsupported operand type" failure
233 mask1.getArray()[2, 2] += 1
234 with self.assertRaises(Exception):
235 self.assertMasksEqual(mask0, mask1)
236 with self.assertRaises(Exception):
237 self.assertMasksEqual(mask1, mask0)
238 with self.assertRaises(Exception):
239 self.assertMaskedImagesEqual(mi0, mi1)
240 with self.assertRaises(Exception):
241 self.assertMaskedImagesEqual(mi1, mi0)
243 skipMask = mi.getMask().Factory(mi.getMask(), True)
244 skipMaskArr = skipMask.getArray()
245 skipMaskArr[:] = 0
246 skipMaskArr[2, 2] = 1
247 self.assertMasksEqual(mask0, mask1, skipMask=skipMaskArr)
248 self.assertMasksEqual(mask0, mask1, skipMask=skipMask)
249 self.assertMaskedImagesAlmostEqual(
250 mi0, mi1, skipMask=skipMaskArr, atol=0, rtol=0)
251 self.assertMaskedImagesAlmostEqual(
252 mi0, mi1, skipMask=skipMask, atol=0, rtol=0)
254 def testAssertImagesAlmostEqual(self):
255 """Test assertImagesAlmostEqual, assertMasksEqual and assertMaskedImagesAlmostEqual
256 """
257 width = 10
258 height = 9
260 for miType in (afwImage.MaskedImageF, afwImage.MaskedImageD, afwImage.MaskedImageI,
261 afwImage.MaskedImageU):
262 mi = makeRampMaskedImageWithNans(width, height, miType)
263 self.checkMaskedImage(mi)
265 for invalidType in (np.zeros([width+1, height]), str, self.assertRaises):
266 mi = makeRampMaskedImageWithNans(width, height, miType)
267 with self.assertRaises(TypeError):
268 self.assertMasksEqual(mi.getMask(), invalidType)
269 with self.assertRaises(TypeError):
270 self.assertMasksEqual(invalidType, mi.getMask())
271 with self.assertRaises(TypeError):
272 self.assertMasksEqual(
273 mi.getMask(), mi.getMask(), skipMask=invalidType)
275 with self.assertRaises(TypeError):
276 self.assertImagesAlmostEqual(mi.getImage(), invalidType)
277 with self.assertRaises(TypeError):
278 self.assertImagesAlmostEqual(invalidType, mi.getImage())
279 with self.assertRaises(TypeError):
280 self.assertImagesAlmostEqual(
281 mi.getImage(), mi.getImage(), skipMask=invalidType)
283 with self.assertRaises(TypeError):
284 self.assertMaskedImagesAlmostEqual(mi, invalidType)
285 with self.assertRaises(TypeError):
286 self.assertMaskedImagesAlmostEqual(invalidType, mi)
287 with self.assertRaises(TypeError):
288 self.assertMaskedImagesAlmostEqual(
289 mi, mi, skipMask=invalidType)
291 with self.assertRaises(TypeError):
292 self.assertMaskedImagesAlmostEqual(
293 mi.getImage(), mi.getImage())
295 def testUnsignedImages(self):
296 """Unsigned images can give incorrect differences unless the test code is careful
297 """
298 image0 = np.zeros([5, 5], dtype=np.uint8)
299 image1 = np.zeros([5, 5], dtype=np.uint8)
300 image0[0, 0] = 1
301 image1[0, 1] = 2
303 # arrays differ by a maximum of 2
304 errMsg1 = imagesDiffer(image0, image1)
305 self.assertIn("maximum absolute error: |0 - 2| = 2 at position (0, 1).", errMsg1)
307 # arrays are equal to within 5
308 self.assertImagesAlmostEqual(image0, image1, atol=5)
311def makeRampMaskedImageWithNans(width, height, imgClass=afwImage.MaskedImageF):
312 """Make a masked image that is a ramp with additional non-finite values
314 Make a masked image with the following additional non-finite values
315 in the variance plane and (if image is of some floating type) image plane:
316 - nan at [0, 0]
317 - inf at [1, 0]
318 - -inf at [0, 1]
319 """
320 mi = makeRampMaskedImage(width, height, imgClass)
322 var = mi.getVariance()
323 var[0, 0] = np.nan
324 var[1, 0] = np.inf
325 var[0, 1] = -np.inf
327 im = mi.getImage()
328 try:
329 np.array([np.nan], dtype=im.getArray().dtype)
330 except Exception:
331 # image plane does not support nan, etc. (presumably an int of some
332 # variety)
333 pass
334 else:
335 # image plane does support nan, etc.
336 im[0, 0] = np.nan
337 im[1, 0] = np.inf
338 im[0, 1] = -np.inf
339 return mi
342def makeRampMaskedImage(width, height, imgClass=afwImage.MaskedImageF):
343 """Make a ramp image of the specified size and image class
345 Image values start from 0 at the lower left corner and increase by 1 along rows
346 Variance values equal image values + 100
347 Mask values equal image values modulo 8 bits (leaving plenty of unused values)
348 """
349 mi = imgClass(width, height)
350 image = mi.getImage()
351 mask = mi.getMask()
352 variance = mi.getVariance()
353 val = 0
354 for yInd in range(height):
355 for xInd in range(width):
356 image[xInd, yInd, afwImage.LOCAL] = val
357 variance[xInd, yInd, afwImage.LOCAL] = val + 100
358 mask[xInd, yInd, afwImage.LOCAL] = val % 0x100
359 val += 1
360 return mi
363class MemoryTester(lsst.utils.tests.MemoryTestCase):
364 pass
367def setup_module(module):
368 lsst.utils.tests.init()
371if __name__ == "__main__": 371 ↛ 372line 371 didn't jump to line 372 because the condition on line 371 was never true
372 lsst.utils.tests.init()
373 unittest.main()