LSSTApplications  11.0-24-g0a022a1,14.0+64,15.0,15.0+1,15.0-1-g14e9bfd,15.0-1-g1eca518,15.0-1-g499c38d,15.0-1-g60afb23,15.0-1-g6668b0b,15.0-1-g788a293,15.0-1-g82223af,15.0-1-ga91101e,15.0-1-gae1598d,15.0-1-gc45031d,15.0-1-gd076f1f,15.0-1-gf4f1c34,15.0-1-gfe1617d,15.0-16-g953e39cab,15.0-2-g2010ef9,15.0-2-g33d94b3,15.0-2-g5218728,15.0-2-g947dc0d,15.0-3-g9103c06,15.0-3-ga03b4ca,15.0-3-ga659d1f3,15.0-3-ga695220+2,15.0-3-gaec6799,15.0-3-gb7a597c,15.0-3-gd5b9ff95,15.0-4-g0478fed+2,15.0-4-g45f767a,15.0-4-gff20472+2,15.0-6-ge2d9597
LSSTDataManagementBasePackage
utils.py
Go to the documentation of this file.
1 from __future__ import absolute_import, division, print_function
2 #
3 # LSST Data Management System
4 # Copyright 2015 LSST Corporation.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
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 LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <http://www.lsstcorp.org/LegalNotices/>.
22 #
23 """Utilities that should be imported into the lsst.afw.geom namespace when lsst.afw.geom is used
24 
25 In the case of the assert functions, importing them makes them available in lsst.utils.tests.TestCase
26 """
27 __all__ = ["wcsAlmostEqualOverBBox"]
28 
29 from builtins import range
30 import itertools
31 import warnings
32 import math
33 
34 import numpy as np
35 
36 import lsst.utils.tests
37 from .angle import arcseconds
38 from .box import Box2D
39 from .coordinates import Point2D
40 from .endpoint import GenericEndpoint, Point2Endpoint, SpherePointEndpoint
41 
42 
43 def extraMsg(msg):
44  """Format extra error message, if any
45  """
46  if msg:
47  return ": " + msg
48  return ""
49 
50 
51 @lsst.utils.tests.inTestCase
52 def assertAnglesAlmostEqual(testCase, ang0, ang1, maxDiff=0.001*arcseconds,
53  ignoreWrap=True, msg="Angles differ"):
54  """!Assert that two angles are almost equal, ignoring wrap differences by default
55 
56  @param[in] testCase unittest.TestCase instance the test is part of;
57  an object supporting one method: fail(self, msgStr)
58  @param[in] ang0 angle 0 (an lsst.afw.geom.Angle)
59  @param[in] ang1 angle 1 (an lsst.afw.geom.Angle)
60  @param[in] maxDiff maximum difference between the two angles (an lsst.afw.geom.Angle)
61  @param[in] ignoreWrap ignore wrap when comparing the angles?
62  - if True then wrap is ignored, e.g. 0 and 360 degrees are considered equal
63  - if False then wrap matters, e.g. 0 and 360 degrees are considered different
64  @param[in] msg exception message prefix; details of the error are appended after ": "
65 
66  @throw AssertionError if the difference is greater than maxDiff
67  """
68  measDiff = ang1 - ang0
69  if ignoreWrap:
70  measDiff = measDiff.wrapCtr()
71  if abs(measDiff) > maxDiff:
72  testCase.fail("%s: measured difference %s arcsec > max allowed %s arcsec" %
73  (msg, measDiff.asArcseconds(), maxDiff.asArcseconds()))
74 
75 
76 @lsst.utils.tests.inTestCase
77 def assertPairsAlmostEqual(testCase, pair0, pair1, maxDiff=1e-7, msg="Pairs differ"):
78  """!Assert that two Cartesian points are almost equal.
79 
80  Each point can be any indexable pair of two floats, including
81  Point2D or Extent2D, a list or a tuple.
82 
83  @warning Does not compare types, just compares values.
84 
85  @param[in] testCase unittest.TestCase instance the test is part of;
86  an object supporting one method: fail(self, msgStr)
87  @param[in] pair0 pair 0 (a pair of floats)
88  @param[in] pair1 pair 1 (a pair of floats)
89  @param[in] maxDiff maximum radial separation between the two points
90  @param[in] msg exception message prefix; details of the error are appended after ": "
91 
92  @throw AssertionError if the radial difference is greater than maxDiff
93  """
94  if len(pair0) != 2:
95  raise RuntimeError("len(pair0)=%s != 2" % (len(pair0),))
96  if len(pair1) != 2:
97  raise RuntimeError("len(pair1)=%s != 2" % (len(pair1),))
98 
99  pairDiff = [float(pair1[i] - pair0[i]) for i in range(2)]
100  measDiff = math.hypot(*pairDiff)
101  if measDiff > maxDiff:
102  testCase.fail("%s: measured radial distance = %s > maxDiff = %s" % (
103  msg, measDiff, maxDiff))
104 
105 
106 @lsst.utils.tests.inTestCase
107 def assertPairListsAlmostEqual(testCase, list0, list1, maxDiff=1e-7, msg=None):
108  """!Assert that two lists of Cartesian points are almost equal
109 
110  Each point can be any indexable pair of two floats, including
111  Point2D or Extent2D, a list or a tuple.
112 
113  @warning Does not compare types, just values.
114 
115  @param[in] testCase unittest.TestCase instance the test is part of;
116  an object supporting one method: fail(self, msgStr)
117  @param[in] list0 list of pairs 0 (each element a pair of floats)
118  @param[in] list1 list of pairs 1
119  @param[in] maxDiff maximum radial separation between the two points
120  @param[in] msg additional information for the error message; appended after ": "
121 
122  @throw AssertionError if the radial difference is greater than maxDiff
123  """
124  testCase.assertEqual(len(list0), len(list1))
125  lenList1 = np.array([len(val) for val in list0])
126  lenList2 = np.array([len(val) for val in list1])
127  testCase.assertTrue(np.all(lenList1 == 2))
128  testCase.assertTrue(np.all(lenList2 == 2))
129 
130  diffArr = np.array([(val0[0] - val1[0], val0[1] - val1[1])
131  for val0, val1 in zip(list0, list1)], dtype=float)
132  sepArr = np.hypot(diffArr[:, 0], diffArr[:, 1])
133  badArr = sepArr > maxDiff
134  if np.any(badArr):
135  maxInd = np.argmax(sepArr)
136  testCase.fail("PairLists differ in %s places; max separation is at %s: %s > %s%s" %
137  (np.sum(badArr), maxInd, sepArr[maxInd], maxDiff, extraMsg(msg)))
138 
139 
140 @lsst.utils.tests.inTestCase
141 def assertSpherePointsAlmostEqual(testCase, sp0, sp1, maxSep=0.001*arcseconds, msg=""):
142  """!Assert that two SpherePoints are almost equal
143 
144  @param[in] testCase unittest.TestCase instance the test is part of;
145  an object supporting one method: fail(self, msgStr)
146  @param[in] sp0 SpherePoint 0
147  @param[in] sp1 SpherePoint 1
148  @param[in] maxSep maximum separation, an lsst.afw.geom.Angle
149  @param[in] msg extra information to be printed with any error message
150  """
151  if sp0.separation(sp1) > maxSep:
152  testCase.fail("Angular separation between %s and %s = %s\" > maxSep = %s\"%s" %
153  (sp0, sp1, sp0.separation(sp1).asArcseconds(), maxSep.asArcseconds(), extraMsg(msg)))
154 
155 
156 @lsst.utils.tests.inTestCase
157 def assertSpherePointListsAlmostEqual(testCase, splist0, splist1, maxSep=0.001*arcseconds, msg=None):
158  """!Assert that two lists of SpherePoints are almost equal
159 
160  @param[in] testCase unittest.TestCase instance the test is part of;
161  an object supporting one method: fail(self, msgStr)
162  @param[in] splist0 list of SpherePoints 0
163  @param[in] splist1 list of SpherePoints 1
164  @param[in] maxSep maximum separation, an lsst.afw.geom.Angle
165  @param[in] msg exception message prefix; details of the error are appended after ": "
166  """
167  testCase.assertEqual(len(splist0), len(splist1), msg=msg)
168  sepArr = np.array([sp0.separation(sp1)
169  for sp0, sp1 in zip(splist0, splist1)])
170  badArr = sepArr > maxSep
171  if np.any(badArr):
172  maxInd = np.argmax(sepArr)
173  testCase.fail("SpherePointLists differ in %s places; max separation is at %s: %s\" > %s\"%s" %
174  (np.sum(badArr), maxInd, sepArr[maxInd].asArcseconds(),
175  maxSep.asArcseconds(), extraMsg(msg)))
176 
177 
178 @lsst.utils.tests.inTestCase
179 def assertBoxesAlmostEqual(testCase, box0, box1, maxDiff=1e-7, msg="Boxes differ"):
180  """!Assert that two boxes (Box2D or Box2I) are almost equal
181 
182  @warning Does not compare types, just compares values.
183 
184  @param[in] testCase unittest.TestCase instance the test is part of;
185  an object supporting one method: fail(self, msgStr)
186  @param[in] box0 box 0
187  @param[in] box1 box 1
188  @param[in] maxDiff maximum radial separation between the min points and max points
189  @param[in] msg exception message prefix; details of the error are appended after ": "
190 
191  @throw AssertionError if the radial difference of the min points or max points is greater than maxDiff
192  """
193  assertPairsAlmostEqual(testCase, box0.getMin(),
194  box1.getMin(), maxDiff=maxDiff, msg=msg + ": min")
195  assertPairsAlmostEqual(testCase, box0.getMax(),
196  box1.getMax(), maxDiff=maxDiff, msg=msg + ": max")
197 
198 
199 def _compareWcsOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01*arcseconds,
200  maxDiffPix=0.01, nx=5, ny=5, doShortCircuit=True):
201  """!Compare two WCS over a rectangular grid of pixel positions
202 
203  @param[in] wcs0 WCS 0 (an lsst.afw.geom.SkyWcs)
204  @param[in] wcs1 WCS 1 (an lsst.afw.geom.SkyWcs)
205  @param[in] bbox boundaries of pixel grid over which to compare the WCSs (an lsst.afw.geom.Box2I or Box2D)
206  @param[in] maxDiffSky maximum separation between sky positions computed using Wcs.pixelToSky
207  (an lsst.afw.geom.Angle)
208  @param[in] maxDiffPix maximum separation between pixel positions computed using Wcs.skyToPixel
209  @param[in] nx number of points in x for the grid of pixel positions
210  @param[in] ny number of points in y for the grid of pixel positions
211  @param[in] doShortCircuit if True then stop at the first error, else test all values in the grid
212  and return information about the worst violations found
213 
214  @return return an empty string if the WCS are sufficiently close; else return a string describing
215  the largest error measured in pixel coordinates (if sky to pixel error was excessive) and sky coordinates
216  (if pixel to sky error was excessive). If doShortCircuit is true then the reported error is likely to be
217  much less than the maximum error across the whole pixel grid.
218  """
219  if nx < 1 or ny < 1:
220  raise RuntimeError(
221  "nx = %s and ny = %s must both be positive" % (nx, ny))
222  if maxDiffSky <= 0*arcseconds:
223  raise RuntimeError("maxDiffSky = %s must be positive" % (maxDiffSky,))
224  if maxDiffPix <= 0:
225  raise RuntimeError("maxDiffPix = %s must be positive" % (maxDiffPix,))
226 
227  bboxd = Box2D(bbox)
228  xList = np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx)
229  yList = np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny)
230  # we don't care about measured error unless it is too large, so initialize
231  # to max allowed
232  measDiffSky = (maxDiffSky, "?") # (sky diff, pix pos)
233  measDiffPix = (maxDiffPix, "?") # (pix diff, sky pos)
234  for x, y in itertools.product(xList, yList):
235  fromPixPos = Point2D(x, y)
236  sky0 = wcs0.pixelToSky(fromPixPos)
237  sky1 = wcs1.pixelToSky(fromPixPos)
238  diffSky = sky0.separation(sky1)
239  if diffSky > measDiffSky[0]:
240  measDiffSky = (diffSky, fromPixPos)
241  if doShortCircuit:
242  break
243 
244  toPixPos0 = wcs0.skyToPixel(sky0)
245  toPixPos1 = wcs1.skyToPixel(sky0)
246  diffPix = math.hypot(*(toPixPos0 - toPixPos1))
247  if diffPix > measDiffPix[0]:
248  measDiffPix = (diffPix, sky0)
249  if doShortCircuit:
250  break
251 
252  msgList = []
253  if measDiffSky[0] > maxDiffSky:
254  msgList.append("%s arcsec max measured sky error > %s arcsec max allowed sky error at pix pos=%s" %
255  (measDiffSky[0].asArcseconds(), maxDiffSky.asArcseconds(), measDiffSky[1]))
256  if measDiffPix[0] > maxDiffPix:
257  msgList.append("%s max measured pix error > %s max allowed pix error at sky pos=%s" %
258  (measDiffPix[0], maxDiffPix, measDiffPix[1]))
259 
260  return "; ".join(msgList)
261 
262 
263 def wcsAlmostEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01*arcseconds,
264  maxDiffPix=0.01, nx=5, ny=5):
265  """!Return True if two WCS are almost equal over a grid of pixel positions, else False
266 
267  @param[in] wcs0 WCS 0 (an lsst.afw.geom.SkyWcs)
268  @param[in] wcs1 WCS 1 (an lsst.afw.geom.SkyWcs)
269  @param[in] bbox boundaries of pixel grid over which to compare the WCSs (an lsst.afw.geom.Box2I or Box2D)
270  @param[in] maxDiffSky maximum separation between sky positions computed using Wcs.pixelToSky
271  (an lsst.afw.geom.Angle)
272  @param[in] maxDiffPix maximum separation between pixel positions computed using Wcs.skyToPixel
273  @param[in] nx number of points in x for the grid of pixel positions
274  @param[in] ny number of points in y for the grid of pixel positions
275  """
276  return not bool(_compareWcsOverBBox(
277  wcs0=wcs0,
278  wcs1=wcs1,
279  bbox=bbox,
280  maxDiffSky=maxDiffSky,
281  maxDiffPix=maxDiffPix,
282  nx=nx,
283  ny=ny,
284  doShortCircuit=True,
285  ))
286 
287 
288 @lsst.utils.tests.inTestCase
289 def assertWcsAlmostEqualOverBBox(testCase, wcs0, wcs1, bbox, maxDiffSky=0.01*arcseconds,
290  maxDiffPix=0.01, nx=5, ny=5, msg="WCSs differ"):
291  """!Assert that two WCS are almost equal over a grid of pixel positions
292 
293  Compare pixelToSky and skyToPixel for two WCS over a rectangular grid of pixel positions.
294  If the WCS are too divergent at any point, call testCase.fail; the message describes
295  the largest error measured in pixel coordinates (if sky to pixel error was excessive)
296  and sky coordinates (if pixel to sky error was excessive) across the entire pixel grid.
297 
298  @param[in] testCase unittest.TestCase instance the test is part of;
299  an object supporting one method: fail(self, msgStr)
300  @param[in] wcs0 WCS 0 (an lsst.afw.geom.SkyWcs)
301  @param[in] wcs1 WCS 1 (an lsst.afw.geom.SkyWcs)
302  @param[in] bbox boundaries of pixel grid over which to compare the WCSs (an lsst.afw.geom.Box2I or Box2D)
303  @param[in] maxDiffSky maximum separation between sky positions computed using Wcs.pixelToSky
304  (an lsst.afw.geom.Angle)
305  @param[in] maxDiffPix maximum separation between pixel positions computed using Wcs.skyToPixel
306  @param[in] nx number of points in x for the grid of pixel positions
307  @param[in] ny number of points in y for the grid of pixel positions
308  @param[in] msg exception message prefix; details of the error are appended after ": "
309  """
310  errMsg = _compareWcsOverBBox(
311  wcs0=wcs0,
312  wcs1=wcs1,
313  bbox=bbox,
314  maxDiffSky=maxDiffSky,
315  maxDiffPix=maxDiffPix,
316  nx=nx,
317  ny=ny,
318  doShortCircuit=False,
319  )
320  if errMsg:
321  testCase.fail("%s: %s" % (msg, errMsg))
322 
323 
324 @lsst.utils.tests.inTestCase
325 def assertWcsNearlyEqualOverBBox(*args, **kwargs):
326  warnings.warn("Deprecated. Use assertWcsAlmostEqualOverBBox",
327  DeprecationWarning, 2)
328  assertWcsAlmostEqualOverBBox(*args, **kwargs)
329 
330 
331 @lsst.utils.tests.inTestCase
332 def makeEndpoints(testCase):
333  """Generate a representative sample of Endpoints.
334 
335  Returns
336  -------
337  x : `list`
338  List of endpoints with enough diversity to exercise Endpoint-related
339  code. Each invocation of this method shall return independent objects.
340  """
341  return [GenericEndpoint(n) for n in range(1, 6)] + \
342  [Point2Endpoint(), SpherePointEndpoint()]
343 
344 
345 @lsst.utils.tests.inTestCase
346 def assertAnglesNearlyEqual(*args, **kwargs):
347  warnings.warn("Deprecated. Use assertAnglesAlmostEqual",
348  DeprecationWarning, 2)
349  assertAnglesAlmostEqual(*args, **kwargs)
350 
351 
352 @lsst.utils.tests.inTestCase
353 def assertPairsNearlyEqual(*args, **kwargs):
354  warnings.warn("Deprecated. Use assertPairsAlmostEqual", DeprecationWarning, 2)
355  assertPairsAlmostEqual(*args, **kwargs)
356 
357 
358 @lsst.utils.tests.inTestCase
359 def assertBoxesNearlyEqual(*args, **kwargs):
360  warnings.warn("Deprecated. Use assertBoxesAlmostEqual", DeprecationWarning, 2)
361  assertBoxesAlmostEqual(*args, **kwargs)
def assertSpherePointsAlmostEqual(testCase, sp0, sp1, maxSep=0.001 *arcseconds, msg="")
Assert that two SpherePoints are almost equal.
Definition: utils.py:141
def makeEndpoints(testCase)
Definition: utils.py:332
def extraMsg(msg)
Definition: utils.py:43
def assertBoxesNearlyEqual(args, kwargs)
Definition: utils.py:359
def assertAnglesNearlyEqual(args, kwargs)
Definition: utils.py:346
def assertWcsNearlyEqualOverBBox(args, kwargs)
Definition: utils.py:325
def assertPairsAlmostEqual(testCase, pair0, pair1, maxDiff=1e-7, msg="Pairs differ")
Assert that two Cartesian points are almost equal.
Definition: utils.py:77
def assertWcsAlmostEqualOverBBox(testCase, wcs0, wcs1, bbox, maxDiffSky=0.01 *arcseconds, maxDiffPix=0.01, nx=5, ny=5, msg="WCSs differ")
Assert that two WCS are almost equal over a grid of pixel positions.
Definition: utils.py:290
Point< double, 2 > Point2D
Definition: Point.h:304
def wcsAlmostEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01 *arcseconds, maxDiffPix=0.01, nx=5, ny=5)
Return True if two WCS are almost equal over a grid of pixel positions, else False.
Definition: utils.py:264
def assertPairsNearlyEqual(args, kwargs)
Definition: utils.py:353
def assertPairListsAlmostEqual(testCase, list0, list1, maxDiff=1e-7, msg=None)
Assert that two lists of Cartesian points are almost equal.
Definition: utils.py:107
def assertBoxesAlmostEqual(testCase, box0, box1, maxDiff=1e-7, msg="Boxes differ")
Assert that two boxes (Box2D or Box2I) are almost equal.
Definition: utils.py:179
def assertAnglesAlmostEqual(testCase, ang0, ang1, maxDiff=0.001 *arcseconds, ignoreWrap=True, msg="Angles differ")
Assert that two angles are almost equal, ignoring wrap differences by default.
Definition: utils.py:53
def assertSpherePointListsAlmostEqual(testCase, splist0, splist1, maxSep=0.001 *arcseconds, msg=None)
Assert that two lists of SpherePoints are almost equal.
Definition: utils.py:157