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
81
83 """Copies the WCS object to a new object.
84
85 @rtype: astWCS.WCS object
86 @return: WCS object
87
88 """
89
90
91 ret=WCS(self.headerSource, self.extensionName, self.mode)
92
93
94 ret.header=self.header.copy()
95 ret.updateFromHeader()
96
97 return ret
98
99
101 """Updates the WCS object using information from WCS.header. This routine should be called whenever
102 changes are made to WCS keywords in WCS.header.
103
104 """
105
106
107
108 cardList=pyfits.CardList()
109 for i in self.header.items():
110 if len(str(i[1])) < 70:
111 cardList.append(pyfits.Card(i[0], i[1]))
112 newHead=pyfits.Header(cards=cardList)
113
114 cardlist=newHead.ascardlist()
115 cardstring=""
116 for card in cardlist:
117 cardstring=cardstring+str(card)
118
119 self.WCSStructure=wcs.wcsinit(cardstring)
120
121
123 """Returns the RA and dec coordinates (in decimal degrees) at the centre of the
124 WCS.
125
126 @rtype: list
127 @return: coordinates in decimal degrees in format [RADeg, decDeg]
128
129 """
130 full=wcs.wcsfull(self.WCSStructure)
131
132 RADeg=full[0]
133 decDeg=full[1]
134
135 return [RADeg, decDeg]
136
137
139 """Returns the width, height of the image according to the WCS in
140 decimal degrees on the sky (i.e., with the projection taken into account).
141
142 @rtype: list
143 @return: width and height of image in decimal degrees on the sky in format
144 [width, height]
145
146 """
147 full=wcs.wcsfull(self.WCSStructure)
148
149 width=full[2]
150 height=full[3]
151
152 return [width, height]
153
154
156 """Returns the half-width, half-height of the image according to the WCS in
157 RA and dec degrees.
158
159 @rtype: list
160 @return: half-width and half-height of image in R.A., dec. decimal degrees
161 in format [half-width, half-height]
162
163 """
164 half=wcs.wcssize(self.WCSStructure)
165
166 width=half[2]
167 height=half[3]
168
169 return [width, height]
170
171
173 """Returns the minimum, maximum WCS coords defined by the size of the parent image (as
174 defined by the NAXIS keywords in the image header).
175
176 @rtype: list
177 @return: [minimum R.A., maximum R.A., minimum Dec., maximum Dec.]
178
179 """
180
181
182 maxX=self.header['NAXIS1']
183 maxY=self.header['NAXIS2']
184 minX=1.0
185 minY=1.0
186
187 if NUMPY_MODE == True:
188 maxX=maxX-1
189 maxY=maxY-1
190 minX=minX-1
191 minY=minY-1
192
193 bottomLeft=self.pix2wcs(minX, minY)
194 topRight=self.pix2wcs(maxX, maxY)
195
196 xCoords=[bottomLeft[0], topRight[0]]
197 yCoords=[bottomLeft[1], topRight[1]]
198 xCoords.sort()
199 yCoords.sort()
200
201 return [xCoords[0], xCoords[1], yCoords[0], yCoords[1]]
202
203
205 """Returns the pixel coordinates corresponding to the input WCS coordinates (given
206 in decimal degrees). RADeg, decDeg can be single floats, or lists or numpy arrays.
207
208 @rtype: list
209 @return: pixel coordinates in format [x, y]
210
211 """
212 try:
213 if list(RADeg) and list(decDeg):
214 pixCoords=[]
215 for ra, dec in zip(RADeg, decDeg):
216 pix=wcs.wcs2pix(self.WCSStructure, ra, dec)
217 if NUMPY_MODE == True:
218 pix[0]=pix[0]-1
219 pix[1]=pix[1]-1
220 pixCoords.append([pix[0], pix[1]])
221 except TypeError:
222 pixCoords=wcs.wcs2pix(self.WCSStructure, RADeg, decDeg)
223 if NUMPY_MODE == True:
224 pixCoords[0]=pixCoords[0]-1
225 pixCoords[1]=pixCoords[1]-1
226 pixCoords=[pixCoords[0], pixCoords[1]]
227
228 return pixCoords
229
230
232 """Returns the WCS coordinates corresponding to the input pixel coordinates.
233
234 @rtype: list
235 @return: WCS coordinates in format [RADeg, decDeg]
236
237 """
238 try:
239 if list(x) and list(y):
240 WCSCoords=[]
241 for xc, yc in zip(x, y):
242 if NUMPY_MODE == True:
243 xc=xc+1
244 yc=yc+1
245 WCSCoords.append(wcs.pix2wcs(self.WCSStructure, xc, yc))
246 except TypeError:
247 if NUMPY_MODE == True:
248 x=x+1
249 y=y+1
250 WCSCoords=wcs.pix2wcs(self.WCSStructure, x, y)
251
252 return WCSCoords
253
254
256 """Returns the rotation angle in degrees around the axis, North through East.
257
258 @rtype: float
259 @return: rotation angle in degrees
260
261 """
262 return self.WCSStructure.rot
263
264
266 """Returns 1 if image is reflected around axis, otherwise returns 0.
267
268 @rtype: int
269 @return: 1 if image is flipped, 0 otherwise
270
271 """
272 return self.WCSStructure.imflip
273
274
276 """Returns the pixel scale of the WCS. This is the average of the x, y pixel scales.
277
278 @rtype: float
279 @return: pixel size in decimal degrees
280
281 """
282
283 avSize=(abs(self.WCSStructure.xinc)+abs(self.WCSStructure.yinc))/2.0
284
285 return avSize
286
287
289 """Returns the pixel scale along the x-axis of the WCS in degrees.
290
291 @rtype: float
292 @return: pixel size in decimal degrees
293
294 """
295
296 avSize=abs(self.WCSStructure.xinc)
297
298 return avSize
299
300
302 """Returns the pixel scale along the y-axis of the WCS in degrees.
303
304 @rtype: float
305 @return: pixel size in decimal degrees
306
307 """
308
309 avSize=abs(self.WCSStructure.yinc)
310
311 return avSize
312
313
315 """Returns the equinox of the WCS.
316
317 @rtype: float
318 @return: equinox of the WCS
319
320 """
321 return self.WCSStructure.equinox
322
323
325 """Returns the epoch of the WCS.
326
327 @rtype: float
328 @return: epoch of the WCS
329
330 """
331 return self.WCSStructure.epoch
332
333
334
335
337 """Finds the minimum, maximum WCS coords that overlap between wcs1 and wcs2. Returns these coordinates,
338 plus the corresponding pixel coordinates for each wcs. Useful for clipping overlapping region between
339 two images.
340
341 @rtype: dictionary
342 @return: dictionary with keys 'overlapWCS' (min, max RA, dec of overlap between wcs1, wcs2)
343 'wcs1Pix', 'wcs2Pix' (pixel coords in each input WCS that correspond to 'overlapWCS' coords)
344
345 """
346
347 mm1=wcs1.getImageMinMaxWCSCoords()
348 mm2=wcs2.getImageMinMaxWCSCoords()
349
350 overlapWCSCoords=[0.0, 0.0, 0.0, 0.0]
351
352
353
354 if mm1[0] - mm2[0] <= 0.0:
355 overlapWCSCoords[0]=mm2[0]
356 else:
357 overlapWCSCoords[0]=mm1[0]
358
359
360 if mm1[1] - mm2[1] <= 0.0:
361 overlapWCSCoords[1]=mm1[1]
362 else:
363 overlapWCSCoords[1]=mm2[1]
364
365
366 if mm1[2] - mm2[2] <= 0.0:
367 overlapWCSCoords[2]=mm2[2]
368 else:
369 overlapWCSCoords[2]=mm1[2]
370
371
372 if mm1[3] - mm2[3] <= 0.0:
373 overlapWCSCoords[3]=mm1[3]
374 else:
375 overlapWCSCoords[3]=mm2[3]
376
377
378 p1Low=wcs1.wcs2pix(overlapWCSCoords[0], overlapWCSCoords[2])
379 p1High=wcs1.wcs2pix(overlapWCSCoords[1], overlapWCSCoords[3])
380 p1=[p1Low[0], p1High[0], p1Low[1], p1High[1]]
381
382 p2Low=wcs2.wcs2pix(overlapWCSCoords[0], overlapWCSCoords[2])
383 p2High=wcs2.wcs2pix(overlapWCSCoords[1], overlapWCSCoords[3])
384 p2=[p2Low[0], p2High[0], p2Low[1], p2High[1]]
385
386 return {'overlapWCS': overlapWCSCoords, 'wcs1Pix': p1, 'wcs2Pix': p2}
387
388
389