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