LSSTApplications  17.0+50,17.0+84,17.0+9,18.0.0+14,18.0.0+2,18.0.0+30,18.0.0+4,18.0.0+9,18.0.0-2-ge43143a+4,18.1.0-1-g0001055,18.1.0-1-g0896a44+6,18.1.0-1-g1349e88+4,18.1.0-1-g2505f39+3,18.1.0-1-g380d4d4+4,18.1.0-1-g5e4b7ea,18.1.0-1-g85f8cd4+3,18.1.0-1-g9a6769a+2,18.1.0-1-ga1a4c1a+2,18.1.0-1-gc037db8,18.1.0-1-gd55f500+1,18.1.0-1-ge10677a+3,18.1.0-10-g73b8679e+7,18.1.0-11-g311e899+3,18.1.0-12-g0d73a3591,18.1.0-12-gc95f69a+3,18.1.0-2-g000ad9a+3,18.1.0-2-g31c43f9+3,18.1.0-2-g9c63283+4,18.1.0-2-gdf0b915+4,18.1.0-2-gf03bb23,18.1.0-3-g2e29e3d+6,18.1.0-3-g52aa583+2,18.1.0-3-g9cb968e+3,18.1.0-4-gd2e8982+6,18.1.0-5-g510c42a+3,18.1.0-5-gaeab27e+4,18.1.0-6-gdda7f3e+6,18.1.0-7-g89824ecc+4,w.2019.32
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 math
30 
31 import numpy as np
32 
33 import lsst.utils.tests
34 import lsst.geom
35 from .endpoint import GenericEndpoint, Point2Endpoint, SpherePointEndpoint
36 
37 
38 def _compareWcsOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01*lsst.geom.arcseconds,
39  maxDiffPix=0.01, nx=5, ny=5, doShortCircuit=True):
40  """Compare two :py:class:`WCS <lsst.afw.geom.SkyWcs>` over a rectangular grid of pixel positions
41 
42  Parameters
43  ----------
44  wcs0 : `lsst.afw.geom.SkyWcs`
45  WCS 0
46  wcs1 : `lsst.afw.geom.SkyWcs`
47  WCS 1
48  bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
49  boundaries of pixel grid over which to compare the WCSs
50  maxDiffSky : `lsst.geom.Angle`
51  maximum separation between sky positions computed using Wcs.pixelToSky
52  maxDiffPix : `float`
53  maximum separation between pixel positions computed using Wcs.skyToPixel
54  nx : `int`
55  number of points in x for the grid of pixel positions
56  ny : `int`
57  number of points in y for the grid of pixel positions
58  doShortCircuit : `bool`
59  if True then stop at the first error, else test all values in the grid
60  and return information about the worst violations found
61 
62  Returns
63  -------
64  msg : `str`
65  an empty string if the WCS are sufficiently close; else return a string describing
66  the largest error measured in pixel coordinates (if sky to pixel error was excessive)
67  and sky coordinates (if pixel to sky error was excessive). If doShortCircuit is true
68  then the reported error is likely to be much less than the maximum error across the
69  whole pixel grid.
70  """
71  if nx < 1 or ny < 1:
72  raise RuntimeError(
73  "nx = %s and ny = %s must both be positive" % (nx, ny))
74  if maxDiffSky < 0*lsst.geom.arcseconds:
75  raise RuntimeError("maxDiffSky = %s must not be negative" % (maxDiffSky,))
76  if maxDiffPix < 0:
77  raise RuntimeError("maxDiffPix = %s must not be negative" % (maxDiffPix,))
78 
79  bboxd = lsst.geom.Box2D(bbox)
80  xList = np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx)
81  yList = np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny)
82  # we don't care about measured error unless it is too large, so initialize
83  # to max allowed
84  measDiffSky = (maxDiffSky, "?") # (sky diff, pix pos)
85  measDiffPix = (maxDiffPix, "?") # (pix diff, sky pos)
86  for x, y in itertools.product(xList, yList):
87  fromPixPos = lsst.geom.Point2D(x, y)
88  sky0 = wcs0.pixelToSky(fromPixPos)
89  sky1 = wcs1.pixelToSky(fromPixPos)
90  diffSky = sky0.separation(sky1)
91  if diffSky > measDiffSky[0]:
92  measDiffSky = (diffSky, fromPixPos)
93  if doShortCircuit:
94  break
95 
96  toPixPos0 = wcs0.skyToPixel(sky0)
97  toPixPos1 = wcs1.skyToPixel(sky0)
98  diffPix = math.hypot(*(toPixPos0 - toPixPos1))
99  if diffPix > measDiffPix[0]:
100  measDiffPix = (diffPix, sky0)
101  if doShortCircuit:
102  break
103 
104  msgList = []
105  if measDiffSky[0] > maxDiffSky:
106  msgList.append("%s arcsec max measured sky error > %s arcsec max allowed sky error at pix pos=%s" %
107  (measDiffSky[0].asArcseconds(), maxDiffSky.asArcseconds(), measDiffSky[1]))
108  if measDiffPix[0] > maxDiffPix:
109  msgList.append("%s max measured pix error > %s max allowed pix error at sky pos=%s" %
110  (measDiffPix[0], maxDiffPix, measDiffPix[1]))
111 
112  return "; ".join(msgList)
113 
114 
115 def wcsAlmostEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01*lsst.geom.arcseconds,
116  maxDiffPix=0.01, nx=5, ny=5):
117  """Test if two :py:class:`WCS <lsst.afw.geom.SkyWcs>` are almost equal over a grid of pixel positions.
118 
119  Parameters
120  ----------
121  wcs0 : `lsst.afw.geom.SkyWcs`
122  WCS 0
123  wcs1 : `lsst.afw.geom.SkyWcs`
124  WCS 1
125  bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
126  boundaries of pixel grid over which to compare the WCSs
127  maxDiffSky : `lsst.geom.Angle`
128  maximum separation between sky positions computed using Wcs.pixelToSky
129  maxDiffPix : `float`
130  maximum separation between pixel positions computed using Wcs.skyToPixel
131  nx : `int`
132  number of points in x for the grid of pixel positions
133  ny : `int`
134  number of points in y for the grid of pixel positions
135 
136  Returns
137  -------
138  almostEqual: `bool`
139  `True` if two WCS are almost equal over a grid of pixel positions, else `False`
140  """
141  return not bool(_compareWcsOverBBox(
142  wcs0=wcs0,
143  wcs1=wcs1,
144  bbox=bbox,
145  maxDiffSky=maxDiffSky,
146  maxDiffPix=maxDiffPix,
147  nx=nx,
148  ny=ny,
149  doShortCircuit=True,
150  ))
151 
152 
153 @lsst.utils.tests.inTestCase
154 def assertWcsAlmostEqualOverBBox(testCase, wcs0, wcs1, bbox, maxDiffSky=0.01*lsst.geom.arcseconds,
155  maxDiffPix=0.01, nx=5, ny=5, msg="WCSs differ"):
156  """Assert that two :py:class:`WCS <lsst.afw.geom.SkyWcs>` are almost equal over a grid of pixel positions
157 
158  Compare pixelToSky and skyToPixel for two WCS over a rectangular grid of pixel positions.
159  If the WCS are too divergent at any point, call testCase.fail; the message describes
160  the largest error measured in pixel coordinates (if sky to pixel error was excessive)
161  and sky coordinates (if pixel to sky error was excessive) across the entire pixel grid.
162 
163  Parameters
164  ----------
165  testCase : `unittest.TestCase`
166  test case the test is part of; an object supporting one method: fail(self, msgStr)
167  wcs0 : `lsst.afw.geom.SkyWcs`
168  WCS 0
169  wcs1 : `lsst.afw.geom.SkyWcs`
170  WCS 1
171  bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
172  boundaries of pixel grid over which to compare the WCSs
173  maxDiffSky : `lsst.geom.Angle`
174  maximum separation between sky positions computed using Wcs.pixelToSky
175  maxDiffPix : `float`
176  maximum separation between pixel positions computed using Wcs.skyToPixel
177  nx : `int`
178  number of points in x for the grid of pixel positions
179  ny : `int`
180  number of points in y for the grid of pixel positions
181  msg : `str`
182  exception message prefix; details of the error are appended after ": "
183  """
184  errMsg = _compareWcsOverBBox(
185  wcs0=wcs0,
186  wcs1=wcs1,
187  bbox=bbox,
188  maxDiffSky=maxDiffSky,
189  maxDiffPix=maxDiffPix,
190  nx=nx,
191  ny=ny,
192  doShortCircuit=False,
193  )
194  if errMsg:
195  testCase.fail("%s: %s" % (msg, errMsg))
196 
197 
198 @lsst.utils.tests.inTestCase
199 def makeEndpoints(testCase):
200  """Generate a representative sample of ``Endpoints``.
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 
207  Returns
208  -------
209  endpoints : `list`
210  List of endpoints with enough diversity to exercise ``Endpoint``-related
211  code. Each invocation of this method shall return independent objects.
212  """
213  return [GenericEndpoint(n) for n in range(1, 6)] + \
214  [Point2Endpoint(), SpherePointEndpoint()]
def makeEndpoints(testCase)
Definition: utils.py:199
A floating-point coordinate rectangle geometry.
Definition: Box.h:305
def assertWcsAlmostEqualOverBBox(testCase, wcs0, wcs1, bbox, maxDiffSky=0.01 *lsst.geom.arcseconds, maxDiffPix=0.01, nx=5, ny=5, msg="WCSs differ")
Definition: utils.py:155
def wcsAlmostEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01 *lsst.geom.arcseconds, maxDiffPix=0.01, nx=5, ny=5)
Definition: utils.py:116