LSSTApplications  11.0-24-g0a022a1,15.0+13,15.0+9,15.0-1-g19261fa+5,15.0-1-g1eca518+15,15.0-1-g60afb23+12,15.0-1-g615e0bb+4,15.0-1-g6668b0b+5,15.0-1-g788a293+12,15.0-1-ga91101e+12,15.0-1-gae1598d+8,15.0-1-gc45031d+15,15.0-1-gd076f1f+12,15.0-1-gdf18595+2,15.0-1-gf4f1c34+8,15.0-2-g100d730+5,15.0-2-g18f3f21+5,15.0-2-g35685a8+6,15.0-2-gf38729e+5,15.0-21-g91b8abf62,15.0-3-g150fc43+14,15.0-3-g6f085af+5,15.0-3-g707930d,15.0-3-g9103c06+8,15.0-3-ga03b4ca+16,15.0-3-gaec6799+5,15.0-3-gb7a597c+12,15.0-3-ge6a6747+5,15.0-4-g45f767a+8,15.0-4-g654b129+10,15.0-4-gf5d1e39,15.0-4-gff20472+15,15.0-5-ga70c291+5,15.0-6-g9a9df217+5,15.0-7-gab4c137+6,15.0-7-gab79a70c+4
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  r"""Assert that two `~lsst.afw.geom.Angle`\ s are almost equal, ignoring wrap differences by default
55 
56  Parameters
57  ----------
58  testCase : `unittest.TestCase`
59  test case the test is part of; an object supporting one method: fail(self, msgStr)
60  ang0 : `lsst.afw.geom.Angle`
61  angle 0
62  ang1 : `an lsst.afw.geom.Angle`
63  angle 1
64  maxDiff : `an lsst.afw.geom.Angle`
65  maximum difference between the two angles
66  ignoreWrap : `bool`
67  ignore wrap when comparing the angles?
68  - if True then wrap is ignored, e.g. 0 and 360 degrees are considered equal
69  - if False then wrap matters, e.g. 0 and 360 degrees are considered different
70  msg : `str`
71  exception message prefix; details of the error are appended after ": "
72 
73  Raises
74  ------
75  AssertionError
76  Raised if the difference is greater than ``maxDiff``
77  """
78  measDiff = ang1 - ang0
79  if ignoreWrap:
80  measDiff = measDiff.wrapCtr()
81  if abs(measDiff) > maxDiff:
82  testCase.fail("%s: measured difference %s arcsec > max allowed %s arcsec" %
83  (msg, measDiff.asArcseconds(), maxDiff.asArcseconds()))
84 
85 
86 @lsst.utils.tests.inTestCase
87 def assertPairsAlmostEqual(testCase, pair0, pair1, maxDiff=1e-7, msg="Pairs differ"):
88  """Assert that two Cartesian points are almost equal.
89 
90  Each point can be any indexable pair of two floats, including
91  Point2D or Extent2D, a list or a tuple.
92 
93  Parameters
94  ----------
95  testCase : `unittest.TestCase`
96  test case the test is part of; an object supporting one method: fail(self, msgStr)
97  pair0 : pair of `float`
98  pair 0
99  pair1 : pair of `floats`
100  pair 1
101  maxDiff : `float`
102  maximum radial separation between the two points
103  msg : `str`
104  exception message prefix; details of the error are appended after ": "
105 
106  Raises
107  ------
108  AssertionError
109  Raised if the radial difference is greater than ``maxDiff``
110 
111  Notes
112  -----
113  .. warning::
114 
115  Does not compare types, just compares values.
116  """
117  if len(pair0) != 2:
118  raise RuntimeError("len(pair0)=%s != 2" % (len(pair0),))
119  if len(pair1) != 2:
120  raise RuntimeError("len(pair1)=%s != 2" % (len(pair1),))
121 
122  pairDiff = [float(pair1[i] - pair0[i]) for i in range(2)]
123  measDiff = math.hypot(*pairDiff)
124  if measDiff > maxDiff:
125  testCase.fail("%s: measured radial distance = %s > maxDiff = %s, pair0=(%r, %r), pair1=(%r, %r)" %
126  (msg, measDiff, maxDiff, pair0[0], pair0[1], pair1[0], pair1[1]))
127 
128 
129 @lsst.utils.tests.inTestCase
130 def assertPairListsAlmostEqual(testCase, list0, list1, maxDiff=1e-7, msg=None):
131  """Assert that two lists of Cartesian points are almost equal
132 
133  Each point can be any indexable pair of two floats, including
134  Point2D or Extent2D, a list or a tuple.
135 
136  Parameters
137  ----------
138  testCase : `unittest.TestCase`
139  test case the test is part of; an object supporting one method: fail(self, msgStr)
140  list0 : `list` of pairs of `float`
141  list of pairs 0
142  list1 : `list` of pairs of `float`
143  list of pairs 1
144  maxDiff : `float`
145  maximum radial separation between the two points
146  msg : `str`
147  additional information for the error message; appended after ": "
148 
149  Raises
150  ------
151  AssertionError
152  Raised if the radial difference is greater than ``maxDiff``
153 
154  Notes
155  -----
156  .. warning::
157 
158  Does not compare types, just values.
159  """
160  testCase.assertEqual(len(list0), len(list1))
161  lenList1 = np.array([len(val) for val in list0])
162  lenList2 = np.array([len(val) for val in list1])
163  testCase.assertTrue(np.all(lenList1 == 2))
164  testCase.assertTrue(np.all(lenList2 == 2))
165 
166  diffArr = np.array([(val0[0] - val1[0], val0[1] - val1[1])
167  for val0, val1 in zip(list0, list1)], dtype=float)
168  sepArr = np.hypot(diffArr[:, 0], diffArr[:, 1])
169  badArr = sepArr > maxDiff
170  if np.any(badArr):
171  maxInd = np.argmax(sepArr)
172  testCase.fail("PairLists differ in %s places; max separation is at %s: %s > %s%s" %
173  (np.sum(badArr), maxInd, sepArr[maxInd], maxDiff, extraMsg(msg)))
174 
175 
176 @lsst.utils.tests.inTestCase
177 def assertSpherePointsAlmostEqual(testCase, sp0, sp1, maxSep=0.001*arcseconds, msg=""):
178  r"""Assert that two `~lsst.afw.geom.SpherePoint`\ s are almost equal
179 
180  Parameters
181  ----------
182  testCase : `unittest.TestCase`
183  test case the test is part of; an object supporting one method: fail(self, msgStr)
184  sp0 : `lsst.afw.geom.SpherePoint`
185  SpherePoint 0
186  sp1 : `lsst.afw.geom.SpherePoint`
187  SpherePoint 1
188  maxSep : `lsst.afw.geom.Angle`
189  maximum separation
190  msg : `str`
191  extra information to be printed with any error message
192  """
193  if sp0.separation(sp1) > maxSep:
194  testCase.fail("Angular separation between %s and %s = %s\" > maxSep = %s\"%s" %
195  (sp0, sp1, sp0.separation(sp1).asArcseconds(), maxSep.asArcseconds(), extraMsg(msg)))
196 
197 
198 @lsst.utils.tests.inTestCase
199 def assertSpherePointListsAlmostEqual(testCase, splist0, splist1, maxSep=0.001*arcseconds, msg=None):
200  r"""Assert that two lists of `~lsst.afw.geom.SpherePoint`\ s are almost equal
201 
202  Parameters
203  ----------
204  testCase : `unittest.TestCase`
205  test case the test is part of; an object supporting one method: fail(self, msgStr)
206  splist0 : `list` of `lsst.afw.geom.SpherePoint`
207  list of SpherePoints 0
208  splist1 : `list` of `lsst.afw.geom.SpherePoint`
209  list of SpherePoints 1
210  maxSep : `lsst.afw.geom.Angle`
211  maximum separation
212  msg : `str`
213  exception message prefix; details of the error are appended after ": "
214  """
215  testCase.assertEqual(len(splist0), len(splist1), msg=msg)
216  sepArr = np.array([sp0.separation(sp1)
217  for sp0, sp1 in zip(splist0, splist1)])
218  badArr = sepArr > maxSep
219  if np.any(badArr):
220  maxInd = np.argmax(sepArr)
221  testCase.fail("SpherePointLists differ in %s places; max separation is at %s: %s\" > %s\"%s" %
222  (np.sum(badArr), maxInd, sepArr[maxInd].asArcseconds(),
223  maxSep.asArcseconds(), extraMsg(msg)))
224 
225 
226 @lsst.utils.tests.inTestCase
227 def assertBoxesAlmostEqual(testCase, box0, box1, maxDiff=1e-7, msg="Boxes differ"):
228  """Assert that two boxes (`~lsst.afw.geom.Box2D` or `~lsst.afw.geom.Box2I`) are almost equal
229 
230  Parameters
231  ----------
232  testCase : `unittest.TestCase`
233  test case the test is part of; an object supporting one method: fail(self, msgStr)
234  box0 : `lsst.afw.geom.Box2D` or `lsst.afw.geom.Box2I`
235  box 0
236  box1 : `lsst.afw.geom.Box2D` or `lsst.afw.geom.Box2I`
237  box 1
238  maxDiff : `float`
239  maximum radial separation between the min points and max points
240  msg : `str`
241  exception message prefix; details of the error are appended after ": "
242 
243  Raises
244  ------
245  AssertionError
246  Raised if the radial difference of the min points or max points is greater than maxDiff
247 
248  Notes
249  -----
250  .. warning::
251 
252  Does not compare types, just compares values.
253  """
254  assertPairsAlmostEqual(testCase, box0.getMin(),
255  box1.getMin(), maxDiff=maxDiff, msg=msg + ": min")
256  assertPairsAlmostEqual(testCase, box0.getMax(),
257  box1.getMax(), maxDiff=maxDiff, msg=msg + ": max")
258 
259 
260 def _compareWcsOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01*arcseconds,
261  maxDiffPix=0.01, nx=5, ny=5, doShortCircuit=True):
262  """Compare two :py:class:`WCS <lsst.afw.geom.SkyWcs>` over a rectangular grid of pixel positions
263 
264  Parameters
265  ----------
266  wcs0 : `lsst.afw.geom.SkyWcs`
267  WCS 0
268  wcs1 : `lsst.afw.geom.SkyWcs`
269  WCS 1
270  bbox : `lsst.afw.geom.Box2I` or `lsst.afw.geom.Box2D`
271  boundaries of pixel grid over which to compare the WCSs
272  maxDiffSky : `lsst.afw.geom.Angle`
273  maximum separation between sky positions computed using Wcs.pixelToSky
274  maxDiffPix : `float`
275  maximum separation between pixel positions computed using Wcs.skyToPixel
276  nx : `int`
277  number of points in x for the grid of pixel positions
278  ny : `int`
279  number of points in y for the grid of pixel positions
280  doShortCircuit : `bool`
281  if True then stop at the first error, else test all values in the grid
282  and return information about the worst violations found
283 
284  Returns
285  -------
286  msg : `str`
287  an empty string if the WCS are sufficiently close; else return a string describing
288  the largest error measured in pixel coordinates (if sky to pixel error was excessive)
289  and sky coordinates (if pixel to sky error was excessive). If doShortCircuit is true
290  then the reported error is likely to be much less than the maximum error across the
291  whole pixel grid.
292  """
293  if nx < 1 or ny < 1:
294  raise RuntimeError(
295  "nx = %s and ny = %s must both be positive" % (nx, ny))
296  if maxDiffSky <= 0*arcseconds:
297  raise RuntimeError("maxDiffSky = %s must be positive" % (maxDiffSky,))
298  if maxDiffPix <= 0:
299  raise RuntimeError("maxDiffPix = %s must be positive" % (maxDiffPix,))
300 
301  bboxd = Box2D(bbox)
302  xList = np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx)
303  yList = np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny)
304  # we don't care about measured error unless it is too large, so initialize
305  # to max allowed
306  measDiffSky = (maxDiffSky, "?") # (sky diff, pix pos)
307  measDiffPix = (maxDiffPix, "?") # (pix diff, sky pos)
308  for x, y in itertools.product(xList, yList):
309  fromPixPos = Point2D(x, y)
310  sky0 = wcs0.pixelToSky(fromPixPos)
311  sky1 = wcs1.pixelToSky(fromPixPos)
312  diffSky = sky0.separation(sky1)
313  if diffSky > measDiffSky[0]:
314  measDiffSky = (diffSky, fromPixPos)
315  if doShortCircuit:
316  break
317 
318  toPixPos0 = wcs0.skyToPixel(sky0)
319  toPixPos1 = wcs1.skyToPixel(sky0)
320  diffPix = math.hypot(*(toPixPos0 - toPixPos1))
321  if diffPix > measDiffPix[0]:
322  measDiffPix = (diffPix, sky0)
323  if doShortCircuit:
324  break
325 
326  msgList = []
327  if measDiffSky[0] > maxDiffSky:
328  msgList.append("%s arcsec max measured sky error > %s arcsec max allowed sky error at pix pos=%s" %
329  (measDiffSky[0].asArcseconds(), maxDiffSky.asArcseconds(), measDiffSky[1]))
330  if measDiffPix[0] > maxDiffPix:
331  msgList.append("%s max measured pix error > %s max allowed pix error at sky pos=%s" %
332  (measDiffPix[0], maxDiffPix, measDiffPix[1]))
333 
334  return "; ".join(msgList)
335 
336 
337 def wcsAlmostEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01*arcseconds,
338  maxDiffPix=0.01, nx=5, ny=5):
339  """Test if two :py:class:`WCS <lsst.afw.geom.SkyWcs>` are almost equal over a grid of pixel positions.
340 
341  Parameters
342  ----------
343  wcs0 : `lsst.afw.geom.SkyWcs`
344  WCS 0
345  wcs1 : `lsst.afw.geom.SkyWcs`
346  WCS 1
347  bbox : `lsst.afw.geom.Box2I` or `lsst.afw.geom.Box2D`
348  boundaries of pixel grid over which to compare the WCSs
349  maxDiffSky : `lsst.afw.geom.Angle`
350  maximum separation between sky positions computed using Wcs.pixelToSky
351  maxDiffPix : `float`
352  maximum separation between pixel positions computed using Wcs.skyToPixel
353  nx : `int`
354  number of points in x for the grid of pixel positions
355  ny : `int`
356  number of points in y for the grid of pixel positions
357 
358  Returns
359  -------
360  almostEqual: `bool`
361  `True` if two WCS are almost equal over a grid of pixel positions, else `False`
362  """
363  return not bool(_compareWcsOverBBox(
364  wcs0=wcs0,
365  wcs1=wcs1,
366  bbox=bbox,
367  maxDiffSky=maxDiffSky,
368  maxDiffPix=maxDiffPix,
369  nx=nx,
370  ny=ny,
371  doShortCircuit=True,
372  ))
373 
374 
375 @lsst.utils.tests.inTestCase
376 def assertWcsAlmostEqualOverBBox(testCase, wcs0, wcs1, bbox, maxDiffSky=0.01*arcseconds,
377  maxDiffPix=0.01, nx=5, ny=5, msg="WCSs differ"):
378  """Assert that two :py:class:`WCS <lsst.afw.geom.SkyWcs>` are almost equal over a grid of pixel positions
379 
380  Compare pixelToSky and skyToPixel for two WCS over a rectangular grid of pixel positions.
381  If the WCS are too divergent at any point, call testCase.fail; the message describes
382  the largest error measured in pixel coordinates (if sky to pixel error was excessive)
383  and sky coordinates (if pixel to sky error was excessive) across the entire pixel grid.
384 
385  Parameters
386  ----------
387  testCase : `unittest.TestCase`
388  test case the test is part of; an object supporting one method: fail(self, msgStr)
389  wcs0 : `lsst.afw.geom.SkyWcs`
390  WCS 0
391  wcs1 : `lsst.afw.geom.SkyWcs`
392  WCS 1
393  bbox : `lsst.afw.geom.Box2I` or `lsst.afw.geom.Box2D`
394  boundaries of pixel grid over which to compare the WCSs
395  maxDiffSky : `lsst.afw.geom.Angle`
396  maximum separation between sky positions computed using Wcs.pixelToSky
397  maxDiffPix : `float`
398  maximum separation between pixel positions computed using Wcs.skyToPixel
399  nx : `int`
400  number of points in x for the grid of pixel positions
401  ny : `int`
402  number of points in y for the grid of pixel positions
403  msg : `str`
404  exception message prefix; details of the error are appended after ": "
405  """
406  errMsg = _compareWcsOverBBox(
407  wcs0=wcs0,
408  wcs1=wcs1,
409  bbox=bbox,
410  maxDiffSky=maxDiffSky,
411  maxDiffPix=maxDiffPix,
412  nx=nx,
413  ny=ny,
414  doShortCircuit=False,
415  )
416  if errMsg:
417  testCase.fail("%s: %s" % (msg, errMsg))
418 
419 
420 @lsst.utils.tests.inTestCase
421 def assertWcsNearlyEqualOverBBox(*args, **kwargs):
422  warnings.warn("Deprecated. Use assertWcsAlmostEqualOverBBox",
423  DeprecationWarning, 2)
424  assertWcsAlmostEqualOverBBox(*args, **kwargs)
425 
426 
427 @lsst.utils.tests.inTestCase
428 def makeEndpoints(testCase):
429  """Generate a representative sample of ``Endpoints``.
430 
431  Parameters
432  ----------
433  testCase : `unittest.TestCase`
434  test case the test is part of; an object supporting one method: fail(self, msgStr)
435 
436  Returns
437  -------
438  endpoints : `list`
439  List of endpoints with enough diversity to exercise ``Endpoint``-related
440  code. Each invocation of this method shall return independent objects.
441  """
442  return [GenericEndpoint(n) for n in range(1, 6)] + \
443  [Point2Endpoint(), SpherePointEndpoint()]
444 
445 
446 @lsst.utils.tests.inTestCase
447 def assertAnglesNearlyEqual(*args, **kwargs):
448  warnings.warn("Deprecated. Use assertAnglesAlmostEqual",
449  DeprecationWarning, 2)
450  assertAnglesAlmostEqual(*args, **kwargs)
451 
452 
453 @lsst.utils.tests.inTestCase
454 def assertPairsNearlyEqual(*args, **kwargs):
455  warnings.warn("Deprecated. Use assertPairsAlmostEqual", DeprecationWarning, 2)
456  assertPairsAlmostEqual(*args, **kwargs)
457 
458 
459 @lsst.utils.tests.inTestCase
460 def assertBoxesNearlyEqual(*args, **kwargs):
461  warnings.warn("Deprecated. Use assertBoxesAlmostEqual", DeprecationWarning, 2)
462  assertBoxesAlmostEqual(*args, **kwargs)
def assertSpherePointsAlmostEqual(testCase, sp0, sp1, maxSep=0.001 *arcseconds, msg="")
Definition: utils.py:177
def makeEndpoints(testCase)
Definition: utils.py:428
def extraMsg(msg)
Definition: utils.py:43
def assertBoxesNearlyEqual(args, kwargs)
Definition: utils.py:460
def assertAnglesNearlyEqual(args, kwargs)
Definition: utils.py:447
def assertWcsNearlyEqualOverBBox(args, kwargs)
Definition: utils.py:421
def assertPairsAlmostEqual(testCase, pair0, pair1, maxDiff=1e-7, msg="Pairs differ")
Definition: utils.py:87
def assertWcsAlmostEqualOverBBox(testCase, wcs0, wcs1, bbox, maxDiffSky=0.01 *arcseconds, maxDiffPix=0.01, nx=5, ny=5, msg="WCSs differ")
Definition: utils.py:377
Point< double, 2 > Point2D
Definition: Point.h:304
def wcsAlmostEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01 *arcseconds, maxDiffPix=0.01, nx=5, ny=5)
Definition: utils.py:338
def assertPairsNearlyEqual(args, kwargs)
Definition: utils.py:454
def assertPairListsAlmostEqual(testCase, list0, list1, maxDiff=1e-7, msg=None)
Definition: utils.py:130
def assertBoxesAlmostEqual(testCase, box0, box1, maxDiff=1e-7, msg="Boxes differ")
Definition: utils.py:227
def assertAnglesAlmostEqual(testCase, ang0, ang1, maxDiff=0.001 *arcseconds, ignoreWrap=True, msg="Angles differ")
Definition: utils.py:53
def assertSpherePointListsAlmostEqual(testCase, splist0, splist1, maxSep=0.001 *arcseconds, msg=None)
Definition: utils.py:199