Package astLib :: Module astWCS
[hide private]
[frames] | no frames]

Source Code for Module astLib.astWCS

  1  # -*- coding: utf-8 -*- 
  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  # if True, -1 from pixel coords to be zero-indexed like numpy. If False, use FITS convention. 
 21  NUMPY_MODE=True 
 22   
 23  # Check for the locale bug when decimal separator isn't '.' (atof used in libwcs) 
 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  #------------------------------------------------------------------------------------------------------------ 
33 -class WCS:
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
82 - def copy(self):
83 """Copies the WCS object to a new object. 84 85 @rtype: astWCS.WCS object 86 @return: WCS object 87 88 """ 89 90 # This only sets up a new WCS object, doesn't do a deep copy 91 ret=WCS(self.headerSource, self.extensionName, self.mode) 92 93 # This fixes copy bug 94 ret.header=self.header.copy() 95 ret.updateFromHeader() 96 97 return ret
98 99
100 - def updateFromHeader(self):
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 # Take out any problematic overly long header keyword values before creating the WCSStructure, 107 # as these cause problems for WCSTools 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
122 - def getCentreWCSCoords(self):
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
138 - def getFullSizeSkyDeg(self):
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
155 - def getHalfSizeDeg(self):
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
172 - def getImageMinMaxWCSCoords(self):
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 # Get size of parent image this WCS is taken from 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
204 - def wcs2pix(self, RADeg, decDeg):
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
231 - def pix2wcs(self, x, y):
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
255 - def getRotationDeg(self):
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
265 - def isFlipped(self):
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
275 - def getPixelSizeDeg(self):
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
288 - def getXPixelSizeDeg(self):
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
301 - def getYPixelSizeDeg(self):
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
314 - def getEquinox(self):
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
324 - def getEpoch(self):
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 # Functions for comparing WCS objects
336 -def findWCSOverlap(wcs1, wcs2):
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 # Note order swapping below is essential 353 # Min RA 354 if mm1[0] - mm2[0] <= 0.0: 355 overlapWCSCoords[0]=mm2[0] 356 else: 357 overlapWCSCoords[0]=mm1[0] 358 359 # Max RA 360 if mm1[1] - mm2[1] <= 0.0: 361 overlapWCSCoords[1]=mm1[1] 362 else: 363 overlapWCSCoords[1]=mm2[1] 364 365 # Min dec. 366 if mm1[2] - mm2[2] <= 0.0: 367 overlapWCSCoords[2]=mm2[2] 368 else: 369 overlapWCSCoords[2]=mm1[2] 370 371 # Max dec. 372 if mm1[3] - mm2[3] <= 0.0: 373 overlapWCSCoords[3]=mm1[3] 374 else: 375 overlapWCSCoords[3]=mm2[3] 376 377 # Get corresponding pixel coords 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