Coverage for python/lsst/sims/GalSimInterface/wcsUtils/WcsUtils.py : 19%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import numpy as np
2from lsst.afw.cameraGeom import TAN_PIXELS, FOCAL_PLANE
3import lsst.afw.geom as afwGeom
4import lsst.geom as LsstGeom
5import lsst.afw.image as afwImage
6import lsst.afw.image.utils as afwImageUtils
7import lsst.daf.base as dafBase
8from lsst.sims.GalSimInterface.wcsUtils import approximateWcs
9from lsst.sims.utils import _nativeLonLatFromPointing
11__all__ = ["tanWcsFromDetector", "tanSipWcsFromDetector"]
14def tanWcsFromDetector(detector_name, camera_wrapper, obs_metadata, epoch):
15 """
16 Take an afw.cameraGeom detector and return a WCS which approximates
17 the focal plane as perfectly flat (i.e. it ignores optical distortions
18 that the telescope may impose on the image)
20 @param [in] detector_name is the name of the detector as stored
21 by afw
23 @param [in] camera_wrapper is an instantionat of a GalSimCameraWrapper
25 @param [in] obs_metadata is an instantiation of ObservationMetaData
26 characterizing the telescope's current pointing
28 @param [in] epoch is the epoch in Julian years of the equinox against
29 which RA and Dec are measured
31 @param [out] tanWcs is an instantiation of afw.image's TanWcs class
32 representing the WCS of the detector as if there were no optical
33 distortions imposed by the telescope.
34 """
36 xTanPixMin, xTanPixMax, \
37 yTanPixMin, yTanPixMax = camera_wrapper.getTanPixelBounds(detector_name)
39 x_center = 0.5*(xTanPixMax+xTanPixMin)
40 y_center = 0.5*(yTanPixMax+yTanPixMin)
42 xPixList = []
43 yPixList = []
44 nameList = []
46 # dx and dy are set somewhat heuristically
47 # setting them equal to 0.1(max-min) lead to errors
48 # on the order of 0.7 arcsec in the WCS
50 dx = 0.5*(xTanPixMax-xTanPixMin)
51 dy = 0.5*(yTanPixMax-yTanPixMin)
53 for xx in np.arange(xTanPixMin, xTanPixMax+0.5*dx, dx):
54 for yy in np.arange(yTanPixMin, yTanPixMax+0.5*dy, dy):
55 xPixList.append(xx)
56 yPixList.append(yy)
57 nameList.append(detector_name)
59 xPixList = np.array(xPixList)
60 yPixList = np.array(yPixList)
62 raList, decList = camera_wrapper._raDecFromPixelCoords(xPixList,
63 yPixList,
64 nameList,
65 obs_metadata=obs_metadata,
66 epoch=epoch,
67 includeDistortion=False)
69 crPix1, crPix2 = camera_wrapper._pixelCoordsFromRaDec(obs_metadata._pointingRA,
70 obs_metadata._pointingDec,
71 chipName=detector_name,
72 obs_metadata=obs_metadata,
73 epoch=epoch,
74 includeDistortion=False)
76 lonList, latList = _nativeLonLatFromPointing(raList, decList,
77 obs_metadata._pointingRA,
78 obs_metadata._pointingDec)
80 # convert from native longitude and latitude to intermediate world coordinates
81 # according to equations (12), (13), (54) and (55) of
82 #
83 # Calabretta and Greisen (2002), A&A 395, p. 1077
84 #
85 radiusList = 180.0/(np.tan(latList)*np.pi)
86 uList = radiusList*np.sin(lonList)
87 vList = -radiusList*np.cos(lonList)
89 delta_xList = xPixList - crPix1
90 delta_yList = yPixList - crPix2
92 bVector = np.array([
93 (delta_xList*uList).sum(),
94 (delta_yList*uList).sum(),
95 (delta_xList*vList).sum(),
96 (delta_yList*vList).sum()
97 ])
99 offDiag = (delta_yList*delta_xList).sum()
100 xsq = np.power(delta_xList, 2).sum()
101 ysq = np.power(delta_yList, 2).sum()
103 aMatrix = np.array([
104 [xsq, offDiag, 0.0, 0.0],
105 [offDiag, ysq, 0.0, 0.0],
106 [0.0, 0.0, xsq, offDiag],
107 [0.0, 0.0, offDiag, ysq]
108 ])
110 coeffs = np.linalg.solve(aMatrix, bVector)
112 fitsHeader = dafBase.PropertyList()
113 fitsHeader.set("RADESYS", "ICRS")
114 fitsHeader.set("EQUINOX", epoch)
115 fitsHeader.set("CRVAL1", obs_metadata.pointingRA)
116 fitsHeader.set("CRVAL2", obs_metadata.pointingDec)
117 fitsHeader.set("CRPIX1", crPix1+1) # the +1 is because LSST uses 0-indexed images
118 fitsHeader.set("CRPIX2", crPix2+1) # FITS files use 1-indexed images
119 fitsHeader.set("CTYPE1", "RA---TAN")
120 fitsHeader.set("CTYPE2", "DEC--TAN")
121 fitsHeader.setDouble("CD1_1", coeffs[0])
122 fitsHeader.setDouble("CD1_2", coeffs[1])
123 fitsHeader.setDouble("CD2_1", coeffs[2])
124 fitsHeader.setDouble("CD2_2", coeffs[3])
126 tanWcs = afwGeom.makeSkyWcs(fitsHeader)
128 return tanWcs
131def tanSipWcsFromDetector(detector_name, camera_wrapper, obs_metadata, epoch,
132 order=3,
133 skyToleranceArcSec=0.001,
134 pixelTolerance=0.01):
135 """
136 Take an afw Detector and approximate its pixel-to-(Ra,Dec) transformation
137 with a TAN-SIP WCs.
139 Definition of the TAN-SIP WCS can be found in Shupe and Hook (2008)
140 http://fits.gsfc.nasa.gov/registry/sip/SIP_distortion_v1_0.pdf
142 @param [in] detector_name is the name of the detector as stored
143 by afw
145 @param [in] camera_wrapper is an instantionat of a GalSimCameraWrapper
147 @param [in] obs_metadata is an instantiation of ObservationMetaData
148 characterizing the telescope's current pointing
150 @param [in] epoch is the epoch in Julian years of the equinox against
151 which RA and Dec are measured
153 @param [in] order is the order of the SIP polynomials to be fit to the
154 optical distortions (default 3)
156 @param [in] skyToleranceArcSec is the maximum allowed error in the fitted
157 world coordinates (in arcseconds). Default 0.001
159 @param [in] pixelTolerance is the maximum allowed error in the fitted
160 pixel coordinates. Default 0.02
162 @param [out] tanSipWcs is an instantiation of afw.image's TanWcs class
163 representing the WCS of the detector with optical distortions parametrized
164 by the SIP polynomials.
165 """
167 bbox = camera_wrapper.getBBox(detector_name)
169 tanWcs = tanWcsFromDetector(detector_name, camera_wrapper, obs_metadata, epoch)
171 tanSipWcs = approximateWcs(tanWcs,
172 order=order,
173 skyTolerance=skyToleranceArcSec*LsstGeom.arcseconds,
174 pixelTolerance=pixelTolerance,
175 detector_name=detector_name,
176 camera_wrapper=camera_wrapper,
177 obs_metadata=obs_metadata)
179 return tanSipWcs