1
2 """module for handling World Coordinate Systems (WCS)
3
4 (c) 2007-2009 Matt Hilton
5
6 U{http://astlib.sourceforge.net}
7
8 This is a higher level interface to some of the routines in PyWCSTools (distributed with astLib).
9 PyWCSTools is a simple SWIG wrapping of WCSTools by Doug Mink
10 (U{http://tdc-www.harvard.edu/software/wcstools/}). It is intended is to make this interface
11 complete enough such that direct use of PyWCSTools is unnecessary.
12
13 """
14
15 import pyfits
16 from PyWCSTools import wcs
17 import numpy
18 import locale
19
20
21 NUMPY_MODE=True
22
23
24 lconv=locale.localeconv()
25 if lconv['decimal_point'] != '.':
26 print "WARNING: decimal point separator is not '.' - astWCS coordinate conversions will not work."
27 print "Workaround: after importing any modules that set the locale (e.g. matplotlib), ",
28 print "do the following:"
29 print " import locale"
30 print " locale.setlocale(locale.LC_NUMERIC, 'C')"
31
32
34 """This class provides methods for accessing information from the World
35 Coordinate System (WCS) contained in the header of a FITS image. Conversions
36 between pixel and WCS coordinates can also be performed.
37
38 To create a WCS object from a FITS file called "test.fits", simply:
39
40 WCS=astWCS.WCS("test.fits")
41
42 Likewise, to create a WCS object from the pyfits.header of "test.fits":
43
44 img=pyfits.open("test.fits")
45 header=img[0].header
46 WCS=astWCS.WCS(header, mode = "pyfits")
47
48 """
49
50 - def __init__(self, headerSource, extensionName = 0, mode = "image"):
51 """Creates a WCS object using either the information contained in the header of the specified
52 .fits image, or from a pyfits.header object. Set mode = "pyfits" if the headerSource
53 is a pyfits.header.
54
55 @type headerSource: string or pyfits.header
56 @param headerSource: filename of input .fits image, or a pyfits.header object
57 @type extensionName: int or string
58 @param extensionName: name or number of .fits extension in which image data
59 is stored
60 @type mode: string
61 @param mode: set to "image" if headerSource is a .fits file name, or set to "pyfits"
62 if headerSource is a pyfits.header object
63
64 @note: The meta data provided by headerSource is stored in WCS.header as a pyfits.header object.
65
66 """
67
68 self.mode=mode
69 self.headerSource=headerSource
70 self.extensionName=extensionName
71
72 if self.mode=="image":
73 img=pyfits.open(self.headerSource)
74 self.header=img[self.extensionName].header
75 img.close()
76 elif self.mode=="pyfits":
77 self.header=headerSource
78
79 self.updateFromHeader()
80
82 """Copies the WCS object to a new object.
83
84 @rtype: astWCS.WCS object
85 @return: WCS object
86
87 """
88
89
90 ret=WCS(self.headerSource, self.extensionName, self.mode)
91
92
93 ret.header=self.header.copy()
94 ret.updateFromHeader()
95
96 return ret
97
99 """Updates the WCS object using information from WCS.header. This routine should
100 be called whenever changes are made to WCS keywords in WCS.header.
101
102 """
103
104 cardlist=self.header.ascardlist()
105
106 cardstring=""
107 for card in cardlist:
108 cardstring=cardstring+str(card)
109 self.WCSStructure=wcs.wcsinit(cardstring)
110
112 """Returns the RA and dec coordinates (in decimal degrees) at the centre of the
113 WCS.
114
115 @rtype: list
116 @return: coordinates in decimal degrees in format [RADeg, decDeg]
117
118 """
119 full=wcs.wcsfull(self.WCSStructure)
120
121 RADeg=full[0]
122 decDeg=full[1]
123
124 return [RADeg, decDeg]
125
127 """Returns the width, height of the image according to the WCS in
128 decimal degrees on the sky (i.e., with the projection taken into account).
129
130 @rtype: list
131 @return: width and height of image in decimal degrees on the sky in format
132 [width, height]
133
134 """
135 full=wcs.wcsfull(self.WCSStructure)
136
137 width=full[2]
138 height=full[3]
139
140 return [width, height]
141
143 """Returns the half-width, half-height of the image according to the WCS in
144 RA and dec degrees.
145
146 @rtype: list
147 @return: half-width and half-height of image in R.A., dec. decimal degrees
148 in format [half-width, half-height]
149
150 """
151 half=wcs.wcssize(self.WCSStructure)
152
153 width=half[2]
154 height=half[3]
155
156 return [width, height]
157
159 """Returns the minimum, maximum WCS coords defined by the size of the parent image (as
160 defined by the NAXIS keywords in the image header).
161
162 @rtype: list
163 @return: [minimum R.A., maximum R.A., minimum Dec., maximum Dec.]
164
165 """
166
167
168 maxX=self.header['NAXIS1']
169 maxY=self.header['NAXIS2']
170 minX=1.0
171 minY=1.0
172
173 if NUMPY_MODE == True:
174 maxX=maxX-1
175 maxY=maxY-1
176 minX=minX-1
177 minY=minY-1
178
179 bottomLeft=self.pix2wcs(minX, minY)
180 topRight=self.pix2wcs(maxX, maxY)
181
182 xCoords=[bottomLeft[0], topRight[0]]
183 yCoords=[bottomLeft[1], topRight[1]]
184 xCoords.sort()
185 yCoords.sort()
186
187 return [xCoords[0], xCoords[1], yCoords[0], yCoords[1]]
188
190 """Returns the pixel coordinates corresponding to the input WCS coordinates (given
191 in decimal degrees). RADeg, decDeg can be single floats, or lists or numpy arrays.
192
193 @rtype: list
194 @return: pixel coordinates in format [x, y]
195
196 """
197 try:
198 if list(RADeg) and list(decDeg):
199 pixCoords=[]
200 for ra, dec in zip(RADeg, decDeg):
201 pix=wcs.wcs2pix(self.WCSStructure, ra, dec)
202 if NUMPY_MODE == True:
203 pix[0]=pix[0]-1
204 pix[1]=pix[1]-1
205 pixCoords.append([pix[0], pix[1]])
206 except TypeError:
207 pixCoords=wcs.wcs2pix(self.WCSStructure, RADeg, decDeg)
208 if NUMPY_MODE == True:
209 pixCoords[0]=pixCoords[0]-1
210 pixCoords[1]=pixCoords[1]-1
211 pixCoords=[pixCoords[0], pixCoords[1]]
212
213 return pixCoords
214
216 """Returns the WCS coordinates corresponding to the input pixel coordinates.
217
218 @rtype: list
219 @return: WCS coordinates in format [RADeg, decDeg]
220
221 """
222 try:
223 if list(x) and list(y):
224 WCSCoords=[]
225 for xc, yc in zip(x, y):
226 if NUMPY_MODE == True:
227 xc=xc+1
228 yc=yc+1
229 WCSCoords.append(wcs.pix2wcs(self.WCSStructure, xc, yc))
230 except TypeError:
231 if NUMPY_MODE == True:
232 x=x+1
233 y=y+1
234 WCSCoords=wcs.pix2wcs(self.WCSStructure, x, y)
235
236 return WCSCoords
237
239 """Returns the rotation angle in degrees around the axis, North through East.
240
241 @rtype: float
242 @return: rotation angle in degrees
243
244 """
245 return self.WCSStructure.rot
246
248 """Returns 1 if image is reflected around axis, otherwise returns 0.
249
250 @rtype: int
251 @return: 1 if image is flipped, 0 otherwise
252
253 """
254 return self.WCSStructure.imflip
255
257 """Returns the pixel scale (in degrees) of the WCS.
258
259 @rtype: float
260 @return: pixel size in decimal degrees
261
262 """
263 avSize=(abs(self.WCSStructure.xinc)+abs(self.WCSStructure.yinc))/2.0
264
265 return avSize
266
268 """Returns the equinox of the WCS.
269
270 @rtype: float
271 @return: equinox of the WCS
272
273 """
274 return self.WCSStructure.equinox
275
277 """Returns the epoch of the WCS.
278
279 @rtype: float
280 @return: epoch of the WCS
281
282 """
283 return self.WCSStructure.epoch
284
285
286
288 """Finds the minimum, maximum R.A. and dec. coords that overlap between wcs1 and wcs2. Returns these
289 coordinates, plus the corresponding pixel coordinates for each wcs. Useful for clipping overlapping
290 region between two images.
291
292 @rtype: dictionary
293 @return: dictionary with keys 'overlapWCS' (min, max RA, dec of overlap between wcs1, wcs2)
294 'wcs1Pix', 'wcs2Pix' (pixel coords in each input WCS that correspond to 'overlapWCS' coords)
295
296 """
297
298 mm1=wcs1.getImageMinMaxWCSCoords()
299 mm2=wcs2.getImageMinMaxWCSCoords()
300
301 overlapWCSCoords=[0.0, 0.0, 0.0, 0.0]
302
303
304
305 if mm1[0] - mm2[0] <= 0.0:
306 overlapWCSCoords[0]=mm2[0]
307 else:
308 overlapWCSCoords[0]=mm1[0]
309
310
311 if mm1[1] - mm2[1] <= 0.0:
312 overlapWCSCoords[1]=mm1[1]
313 else:
314 overlapWCSCoords[1]=mm2[1]
315
316
317 if mm1[2] - mm2[2] <= 0.0:
318 overlapWCSCoords[2]=mm2[2]
319 else:
320 overlapWCSCoords[2]=mm1[2]
321
322
323 if mm1[3] - mm2[3] <= 0.0:
324 overlapWCSCoords[3]=mm1[3]
325 else:
326 overlapWCSCoords[3]=mm2[3]
327
328
329 p1Low=wcs1.wcs2pix(overlapWCSCoords[0], overlapWCSCoords[2])
330 p1High=wcs1.wcs2pix(overlapWCSCoords[1], overlapWCSCoords[3])
331 p1=[p1Low[0], p1High[0], p1Low[1], p1High[1]]
332
333 p2Low=wcs2.wcs2pix(overlapWCSCoords[0], overlapWCSCoords[2])
334 p2High=wcs2.wcs2pix(overlapWCSCoords[1], overlapWCSCoords[3])
335 p2=[p2Low[0], p2High[0], p2Low[1], p2High[1]]
336
337 return {'overlapWCS': overlapWCSCoords, 'wcs1Pix': p1, 'wcs2Pix': p2}
338
339
340