LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
LSSTDataManagementBasePackage
basicUtils.py
Go to the documentation of this file.
1 from __future__ import absolute_import, division
2 from builtins import str
3 #
4 # LSST Data Management System
5 # Copyright 2008-2016 LSST Corporation.
6 #
7 # This product includes software developed by the
8 # LSST Project (http://www.lsst.org/).
9 #
10 # This program is free software: you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation, either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the LSST License Statement and
21 # the GNU General Public License along with this program. If not,
22 # see <http://www.lsstcorp.org/LegalNotices/>.
23 #
24 
25 """Application Framework image-related classes including Image, Mask and MaskedImage
26 """
27 import itertools
28 import math
29 
30 import numpy
31 
32 import lsst.utils.tests
33 import lsst.afw.geom as afwGeom
34 from . import imageLib
35 
36 __all__ = ["makeImageFromArray", "makeMaskFromArray", "makeMaskedImageFromArrays",
37  "wcsNearlyEqualOverBBox", "assertWcsNearlyEqualOverBBox"]
38 
39 suffixes = {str(numpy.uint16): "U", str(numpy.int32): "I", str(numpy.float32): "F", str(numpy.float64): "D"}
40 
41 def makeImageFromArray(array):
42  """Construct an Image from a NumPy array, inferring the Image type from the NumPy type.
43  Return None if input is None.
44  """
45  if array is None:
46  return None
47  cls = getattr(imageLib, "Image%s" % (suffixes[str(array.dtype.type)],))
48  return cls(array)
49 
50 def makeMaskFromArray(array):
51  """Construct an Mask from a NumPy array, inferring the Mask type from the NumPy type.
52  Return None if input is None.
53  """
54  if array is None:
55  return None
56  cls = getattr(imageLib, "Mask%s" % (suffixes[str(array.dtype.type)],))
57  return cls(array)
58 
59 def makeMaskedImageFromArrays(image, mask=None, variance=None):
60  """Construct a MaskedImage from three NumPy arrays, inferring the MaskedImage types from the NumPy types.
61  """
62  cls = getattr(imageLib, "MaskedImage%s" % (suffixes[str(image.dtype.type)],))
63  return cls(makeImageFromArray(image), makeMaskFromArray(mask), makeImageFromArray(variance))
64 
65 def _compareWcsOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01*afwGeom.arcseconds,
66  maxDiffPix=0.01, nx=5, ny=5, doShortCircuit=True):
67  """!Compare two WCS over a rectangular grid of pixel positions
68 
69  @param[in] wcs0 WCS 0 (an lsst.afw.image.Wcs)
70  @param[in] wcs1 WCS 1 (an lsst.afw.image.Wcs)
71  @param[in] bbox boundaries of pixel grid over which to compare the WCSs (an lsst.afw.geom.Box2I or Box2D)
72  @param[in] maxDiffSky maximum separation between sky positions computed using Wcs.pixelToSky
73  (an lsst.afw.geom.Angle)
74  @param[in] maxDiffPix maximum separation between pixel positions computed using Wcs.skyToPixel
75  @param[in] nx number of points in x for the grid of pixel positions
76  @param[in] ny number of points in y for the grid of pixel positions
77  @param[in] doShortCircuit if True then stop at the first error, else test all values in the grid
78  and return information about the worst violations found
79 
80  @return return an empty string if the WCS are sufficiently close; else return a string describing
81  the largest error measured in pixel coordinates (if sky to pixel error was excessive) and sky coordinates
82  (if pixel to sky error was excessive). If doShortCircuit is true then the reported error is likely to be
83  much less than the maximum error across the whole pixel grid.
84  """
85  if nx < 1 or ny < 1:
86  raise RuntimeError("nx = %s and ny = %s must both be positive" % (nx, ny))
87  if maxDiffSky <= 0*afwGeom.arcseconds:
88  raise RuntimeError("maxDiffSky = %s must be positive" % (maxDiffSky,))
89  if maxDiffPix <= 0:
90  raise RuntimeError("maxDiffPix = %s must be positive" % (maxDiffPix,))
91 
92  bboxd = afwGeom.Box2D(bbox)
93  xList = numpy.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx)
94  yList = numpy.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny)
95  # we don't care about measured error unless it is too large, so initialize to max allowed
96  measDiffSky = (maxDiffSky, "?") # (sky diff, pix pos)
97  measDiffPix = (maxDiffPix, "?") # (pix diff, sky pos)
98  for x, y in itertools.product(xList, yList):
99  fromPixPos = afwGeom.Point2D(x, y)
100  sky0 = wcs0.pixelToSky(fromPixPos)
101  sky1 = wcs1.pixelToSky(fromPixPos)
102  diffSky = sky0.angularSeparation(sky1)
103  if diffSky > measDiffSky[0]:
104  measDiffSky = (diffSky, fromPixPos)
105  if doShortCircuit:
106  break
107 
108  toPixPos0 = wcs0.skyToPixel(sky0)
109  toPixPos1 = wcs1.skyToPixel(sky0)
110  diffPix = math.hypot(*(toPixPos0 - toPixPos1))
111  if diffPix > measDiffPix[0]:
112  measDiffPix = (diffPix, sky0)
113  if doShortCircuit:
114  break
115 
116  msgList = []
117  if measDiffSky[0] > maxDiffSky:
118  msgList.append("%s arcsec max measured sky error > %s arcsec max allowed sky error at pix pos=%s" %
119  (measDiffSky[0].asArcseconds(), maxDiffSky.asArcseconds(), measDiffSky[1]))
120  if measDiffPix[0] > maxDiffPix:
121  msgList.append("%s max measured pix error > %s max allowed pix error at sky pos=%s" %
122  (measDiffPix[0], maxDiffPix, measDiffPix[1]))
123 
124  return "; ".join(msgList)
125 
126 def wcsNearlyEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.01*afwGeom.arcseconds,
127  maxDiffPix=0.01, nx=5, ny=5):
128  """!Return True if two WCS are nearly equal over a grid of pixel positions, else False
129 
130  @param[in] wcs0 WCS 0 (an lsst.afw.image.Wcs)
131  @param[in] wcs1 WCS 1 (an lsst.afw.image.Wcs)
132  @param[in] bbox boundaries of pixel grid over which to compare the WCSs (an lsst.afw.geom.Box2I or Box2D)
133  @param[in] maxDiffSky maximum separation between sky positions computed using Wcs.pixelToSky
134  (an lsst.afw.geom.Angle)
135  @param[in] maxDiffPix maximum separation between pixel positions computed using Wcs.skyToPixel
136  @param[in] nx number of points in x for the grid of pixel positions
137  @param[in] ny number of points in y for the grid of pixel positions
138  """
139  return not bool(_compareWcsOverBBox(
140  wcs0 = wcs0,
141  wcs1 = wcs1,
142  bbox = bbox,
143  maxDiffSky = maxDiffSky,
144  maxDiffPix = maxDiffPix,
145  nx = nx,
146  ny = ny,
147  doShortCircuit = True,
148  ))
149 
150 @lsst.utils.tests.inTestCase
151 def assertWcsNearlyEqualOverBBox(testCase, wcs0, wcs1, bbox, maxDiffSky=0.01*afwGeom.arcseconds,
152  maxDiffPix=0.01, nx=5, ny=5, msg="WCSs differ"):
153  """!Compare pixelToSky and skyToPixel for two WCS over a rectangular grid of pixel positions
154 
155  If the WCS are too divergent, call testCase.fail; the message describes the largest error measured
156  in pixel coordinates (if sky to pixel error was excessive) and sky coordinates (if pixel to sky error
157  was excessive) across the entire pixel grid.
158 
159  @param[in] testCase unittest.TestCase instance the test is part of;
160  an object supporting one method: fail(self, msgStr)
161  @param[in] wcs0 WCS 0 (an lsst.afw.image.Wcs)
162  @param[in] wcs1 WCS 1 (an lsst.afw.image.Wcs)
163  @param[in] bbox boundaries of pixel grid over which to compare the WCSs (an lsst.afw.geom.Box2I or Box2D)
164  @param[in] maxDiffSky maximum separation between sky positions computed using Wcs.pixelToSky
165  (an lsst.afw.geom.Angle)
166  @param[in] maxDiffPix maximum separation between pixel positions computed using Wcs.skyToPixel
167  @param[in] nx number of points in x for the grid of pixel positions
168  @param[in] ny number of points in y for the grid of pixel positions
169  @param[in] msg exception message prefix; details of the error are appended after ": "
170  """
171  errMsg = _compareWcsOverBBox(
172  wcs0 = wcs0,
173  wcs1 = wcs1,
174  bbox = bbox,
175  maxDiffSky = maxDiffSky,
176  maxDiffPix = maxDiffPix,
177  nx = nx,
178  ny = ny,
179  doShortCircuit = False,
180  )
181  if errMsg:
182  testCase.fail("%s: %s" % (msg, errMsg))
def wcsNearlyEqualOverBBox
Return True if two WCS are nearly equal over a grid of pixel positions, else False.
Definition: basicUtils.py:127
def _compareWcsOverBBox
Compare two WCS over a rectangular grid of pixel positions.
Definition: basicUtils.py:66
A floating-point coordinate rectangle geometry.
Definition: Box.h:271
def assertWcsNearlyEqualOverBBox
Compare pixelToSky and skyToPixel for two WCS over a rectangular grid of pixel positions.
Definition: basicUtils.py:152