LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
utils.py
Go to the documentation of this file.
1 # This file is part of afw.
2 #
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (https://www.lsst.org).
6 # See the COPYRIGHT file at the top-level directory of this distribution
7 # for details of code ownership.
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 GNU General Public License
20 # along with this program. If not, see <https://www.gnu.org/licenses/>.
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 ._geom 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(f"nx = {nx} and ny = {ny} must both be positive")
73  if maxDiffSky < 0*lsst.geom.arcseconds:
74  raise RuntimeError(f"maxDiffSky = {maxDiffSky} must not be negative")
75  if maxDiffPix < 0:
76  raise RuntimeError(f"maxDiffPix = {maxDiffPix} must not be negative")
77 
78  bboxd = lsst.geom.Box2D(bbox)
79  xList = np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx)
80  yList = np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny)
81  # we don't care about measured error unless it is too large, so initialize
82  # to max allowed
83  measDiffSky = (maxDiffSky, "?") # (sky diff, pix pos)
84  measDiffPix = (maxDiffPix, "?") # (pix diff, sky pos)
85  for x, y in itertools.product(xList, yList):
86  fromPixPos = lsst.geom.Point2D(x, y)
87  sky0 = wcs0.pixelToSky(fromPixPos)
88  sky1 = wcs1.pixelToSky(fromPixPos)
89  diffSky = sky0.separation(sky1)
90  if diffSky > measDiffSky[0]:
91  measDiffSky = (diffSky, fromPixPos)
92  if doShortCircuit:
93  break
94 
95  toPixPos0 = wcs0.skyToPixel(sky0)
96  toPixPos1 = wcs1.skyToPixel(sky0)
97  diffPix = math.hypot(*(toPixPos0 - toPixPos1))
98  if diffPix > measDiffPix[0]:
99  measDiffPix = (diffPix, sky0)
100  if doShortCircuit:
101  break
102 
103  msgList = []
104  if measDiffSky[0] > maxDiffSky:
105  msgList.append(f"{measDiffSky[0].asArcseconds()} arcsec max measured sky error "
106  f"> {maxDiffSky.asArcseconds()} arcsec max allowed sky error "
107  f"at pix pos=measDiffSky[1]")
108  if measDiffPix[0] > maxDiffPix:
109  msgList.append(f"{measDiffPix[0]} max measured pix error "
110  f"> {maxDiffPix} max allowed pix error "
111  f"at sky pos={measDiffPix[1]}")
112 
113  return "; ".join(msgList)
114 
115 
116 def wcsAlmostEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01*lsst.geom.arcseconds,
117  maxDiffPix=0.01, nx=5, ny=5):
118  """Test if two :py:class:`WCS <lsst.afw.geom.SkyWcs>` are almost equal over a grid of pixel positions.
119 
120  Parameters
121  ----------
122  wcs0 : `lsst.afw.geom.SkyWcs`
123  WCS 0
124  wcs1 : `lsst.afw.geom.SkyWcs`
125  WCS 1
126  bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
127  boundaries of pixel grid over which to compare the WCSs
128  maxDiffSky : `lsst.geom.Angle`
129  maximum separation between sky positions computed using Wcs.pixelToSky
130  maxDiffPix : `float`
131  maximum separation between pixel positions computed using Wcs.skyToPixel
132  nx : `int`
133  number of points in x for the grid of pixel positions
134  ny : `int`
135  number of points in y for the grid of pixel positions
136 
137  Returns
138  -------
139  almostEqual: `bool`
140  `True` if two WCS are almost equal over a grid of pixel positions, else `False`
141  """
142  return not bool(_compareWcsOverBBox(
143  wcs0=wcs0,
144  wcs1=wcs1,
145  bbox=bbox,
146  maxDiffSky=maxDiffSky,
147  maxDiffPix=maxDiffPix,
148  nx=nx,
149  ny=ny,
150  doShortCircuit=True,
151  ))
152 
153 
154 @lsst.utils.tests.inTestCase
155 def assertWcsAlmostEqualOverBBox(testCase, wcs0, wcs1, bbox, maxDiffSky=0.01*lsst.geom.arcseconds,
156  maxDiffPix=0.01, nx=5, ny=5, msg="WCSs differ"):
157  """Assert that two :py:class:`WCS <lsst.afw.geom.SkyWcs>` are almost equal over a grid of pixel positions
158 
159  Compare pixelToSky and skyToPixel for two WCS over a rectangular grid of pixel positions.
160  If the WCS are too divergent at any point, call testCase.fail; the message describes
161  the largest error measured in pixel coordinates (if sky to pixel error was excessive)
162  and sky coordinates (if pixel to sky error was excessive) across the entire pixel grid.
163 
164  Parameters
165  ----------
166  testCase : `unittest.TestCase`
167  test case the test is part of; an object supporting one method: fail(self, msgStr)
168  wcs0 : `lsst.afw.geom.SkyWcs`
169  WCS 0
170  wcs1 : `lsst.afw.geom.SkyWcs`
171  WCS 1
172  bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D`
173  boundaries of pixel grid over which to compare the WCSs
174  maxDiffSky : `lsst.geom.Angle`
175  maximum separation between sky positions computed using Wcs.pixelToSky
176  maxDiffPix : `float`
177  maximum separation between pixel positions computed using Wcs.skyToPixel
178  nx : `int`
179  number of points in x for the grid of pixel positions
180  ny : `int`
181  number of points in y for the grid of pixel positions
182  msg : `str`
183  exception message prefix; details of the error are appended after ": "
184  """
185  errMsg = _compareWcsOverBBox(
186  wcs0=wcs0,
187  wcs1=wcs1,
188  bbox=bbox,
189  maxDiffSky=maxDiffSky,
190  maxDiffPix=maxDiffPix,
191  nx=nx,
192  ny=ny,
193  doShortCircuit=False,
194  )
195  if errMsg:
196  testCase.fail(f"{msg}: {errMsg}")
197 
198 
199 @lsst.utils.tests.inTestCase
200 def makeEndpoints(testCase):
201  """Generate a representative sample of ``Endpoints``.
202 
203  Parameters
204  ----------
205  testCase : `unittest.TestCase`
206  test case the test is part of; an object supporting one method: fail(self, msgStr)
207 
208  Returns
209  -------
210  endpoints : `list`
211  List of endpoints with enough diversity to exercise ``Endpoint``-related
212  code. Each invocation of this method shall return independent objects.
213  """
214  return [GenericEndpoint(n) for n in range(1, 6)] + \
215  [Point2Endpoint(), SpherePointEndpoint()]
A floating-point coordinate rectangle geometry.
Definition: Box.h:413
def wcsAlmostEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01 *lsst.geom.arcseconds, maxDiffPix=0.01, nx=5, ny=5)
Definition: utils.py:117
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:156
def makeEndpoints(testCase)
Definition: utils.py:200