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-2012 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  @var NUMPY_MODE: If True (default), pixel coordinates accepted/returned by routines such as  
 14  L{astWCS.WCS.pix2wcs}, L{astWCS.WCS.wcs2pix} have (0, 0) as the origin. Set to False to make these routines  
 15  accept/return pixel coords with (1, 1) as the origin (i.e. to match the FITS convention, default behaviour  
 16  prior to astLib version 0.3.0). 
 17  @type NUMPY_MODE: bool 
 18   
 19  """ 
 20  #------------------------------------------------------------------------------------------------------------ 
 21  import pyfits 
 22  from PyWCSTools import wcs 
 23  import numpy 
 24  import locale 
 25   
 26  # if True, -1 from pixel coords to be zero-indexed like numpy. If False, use FITS convention. 
 27  NUMPY_MODE=True 
 28   
 29  # Check for the locale bug when decimal separator isn't '.' (atof used in libwcs) 
 30  lconv=locale.localeconv() 
 31  if lconv['decimal_point'] != '.': 
 32      print "WARNING: decimal point separator is not '.' - astWCS coordinate conversions will not work." 
 33      print "Workaround: after importing any modules that set the locale (e.g. matplotlib), ", 
 34      print "do the following:" 
 35      print "   import locale" 
 36      print "   locale.setlocale(locale.LC_NUMERIC, 'C')" 
 37   
 38  #------------------------------------------------------------------------------------------------------------ 
39 -class WCS:
40 """This class provides methods for accessing information from the World 41 Coordinate System (WCS) contained in the header of a FITS image. Conversions 42 between pixel and WCS coordinates can also be performed. 43 44 To create a WCS object from a FITS file called "test.fits", simply: 45 46 WCS=astWCS.WCS("test.fits") 47 48 Likewise, to create a WCS object from the pyfits.header of "test.fits": 49 50 img=pyfits.open("test.fits") 51 header=img[0].header 52 WCS=astWCS.WCS(header, mode = "pyfits") 53 54 """ 55
56 - def __init__(self, headerSource, extensionName = 0, mode = "image"):
57 """Creates a WCS object using either the information contained in the header of the specified 58 .fits image, or from a pyfits.header object. Set mode = "pyfits" if the headerSource 59 is a pyfits.header. 60 61 @type headerSource: string or pyfits.header 62 @param headerSource: filename of input .fits image, or a pyfits.header object 63 @type extensionName: int or string 64 @param extensionName: name or number of .fits extension in which image data 65 is stored 66 @type mode: string 67 @param mode: set to "image" if headerSource is a .fits file name, or set to "pyfits" 68 if headerSource is a pyfits.header object 69 70 @note: The meta data provided by headerSource is stored in WCS.header as a pyfits.header object. 71 72 """ 73 74 self.mode=mode 75 self.headerSource=headerSource 76 self.extensionName=extensionName 77 78 if self.mode=="image": 79 img=pyfits.open(self.headerSource) 80 img.verify('silentfix') # solves problems with non-standard headers 81 self.header=img[self.extensionName].header 82 img.close() 83 elif self.mode=="pyfits": 84 self.header=headerSource 85 86 self.updateFromHeader()
87 88
89 - def copy(self):
90 """Copies the WCS object to a new object. 91 92 @rtype: astWCS.WCS object 93 @return: WCS object 94 95 """ 96 97 # This only sets up a new WCS object, doesn't do a deep copy 98 ret=WCS(self.headerSource, self.extensionName, self.mode) 99 100 # This fixes copy bug 101 ret.header=self.header.copy() 102 ret.updateFromHeader() 103 104 return ret
105 106
107 - def updateFromHeader(self):
108 """Updates the WCS object using information from WCS.header. This routine should be called whenever 109 changes are made to WCS keywords in WCS.header. 110 111 """ 112 113 # Take out any problematic overly long header keyword values before creating the WCSStructure, 114 # as these cause problems for WCSTools 115 cardList=pyfits.CardList() 116 for i in self.header.items(): 117 if len(str(i[1])) < 70: 118 if len(str(i[0])) <= 8: 119 cardList.append(pyfits.Card(i[0], i[1])) 120 else: 121 cardList.append(pyfits.Card('HIERARCH '+i[0], i[1])) 122 newHead=pyfits.Header(cards=cardList) 123 124 cardlist=newHead.ascardlist() 125 cardstring="" 126 for card in cardlist: 127 cardstring=cardstring+str(card) 128 129 self.WCSStructure=wcs.wcsinit(cardstring)
130 131
132 - def getCentreWCSCoords(self):
133 """Returns the RA and dec coordinates (in decimal degrees) at the centre of the 134 WCS. 135 136 @rtype: list 137 @return: coordinates in decimal degrees in format [RADeg, decDeg] 138 139 """ 140 full=wcs.wcsfull(self.WCSStructure) 141 142 RADeg=full[0] 143 decDeg=full[1] 144 145 return [RADeg, decDeg]
146 147
148 - def getFullSizeSkyDeg(self):
149 """Returns the width, height of the image according to the WCS in 150 decimal degrees on the sky (i.e., with the projection taken into account). 151 152 @rtype: list 153 @return: width and height of image in decimal degrees on the sky in format 154 [width, height] 155 156 """ 157 full=wcs.wcsfull(self.WCSStructure) 158 159 width=full[2] 160 height=full[3] 161 162 return [width, height]
163 164
165 - def getHalfSizeDeg(self):
166 """Returns the half-width, half-height of the image according to the WCS in 167 RA and dec degrees. 168 169 @rtype: list 170 @return: half-width and half-height of image in R.A., dec. decimal degrees 171 in format [half-width, half-height] 172 173 """ 174 half=wcs.wcssize(self.WCSStructure) 175 176 width=half[2] 177 height=half[3] 178 179 return [width, height]
180 181
182 - def getImageMinMaxWCSCoords(self):
183 """Returns the minimum, maximum WCS coords defined by the size of the parent image (as 184 defined by the NAXIS keywords in the image header). 185 186 @rtype: list 187 @return: [minimum R.A., maximum R.A., minimum Dec., maximum Dec.] 188 189 """ 190 191 # Get size of parent image this WCS is taken from 192 maxX=self.header['NAXIS1'] 193 maxY=self.header['NAXIS2'] 194 minX=1.0 195 minY=1.0 196 197 if NUMPY_MODE == True: 198 maxX=maxX-1 199 maxY=maxY-1 200 minX=minX-1 201 minY=minY-1 202 203 bottomLeft=self.pix2wcs(minX, minY) 204 topRight=self.pix2wcs(maxX, maxY) 205 206 xCoords=[bottomLeft[0], topRight[0]] 207 yCoords=[bottomLeft[1], topRight[1]] 208 xCoords.sort() 209 yCoords.sort() 210 211 return [xCoords[0], xCoords[1], yCoords[0], yCoords[1]]
212 213
214 - def wcs2pix(self, RADeg, decDeg):
215 """Returns the pixel coordinates corresponding to the input WCS coordinates (given 216 in decimal degrees). RADeg, decDeg can be single floats, or lists or numpy arrays. 217 218 @rtype: list 219 @return: pixel coordinates in format [x, y] 220 221 """ 222 223 if type(RADeg) == numpy.ndarray or type(RADeg) == list: 224 if type(decDeg) == numpy.ndarray or type(decDeg) == list: 225 pixCoords=[] 226 for ra, dec in zip(RADeg, decDeg): 227 pix=wcs.wcs2pix(self.WCSStructure, float(ra), float(dec)) 228 # Below handles CEA wraparounds 229 if pix[0] < 1: 230 xTest=(self.header['CRPIX1'])-(ra-360.0)/self.getXPixelSizeDeg() 231 if xTest >= 1 and xTest < self.header['NAXIS1']: 232 pix[0]=xTest 233 if NUMPY_MODE == True: 234 pix[0]=pix[0]-1 235 pix[1]=pix[1]-1 236 pixCoords.append([pix[0], pix[1]]) 237 else: 238 pixCoords=wcs.wcs2pix(self.WCSStructure, float(RADeg), float(decDeg)) 239 # Below handles CEA wraparounds 240 if pixCoords[0] < 1: 241 xTest=(self.header['CRPIX1'])-(RADeg-360.0)/self.getXPixelSizeDeg() 242 if xTest >= 1 and xTest < self.header['NAXIS1']: 243 pixCoords[0]=xTest 244 if NUMPY_MODE == True: 245 pixCoords[0]=pixCoords[0]-1 246 pixCoords[1]=pixCoords[1]-1 247 pixCoords=[pixCoords[0], pixCoords[1]] 248 249 return pixCoords
250 251
252 - def pix2wcs(self, x, y):
253 """Returns the WCS coordinates corresponding to the input pixel coordinates. 254 255 @rtype: list 256 @return: WCS coordinates in format [RADeg, decDeg] 257 258 """ 259 if type(x) == numpy.ndarray or type(x) == list: 260 if type(y) == numpy.ndarray or type(y) == list: 261 WCSCoords=[] 262 for xc, yc in zip(x, y): 263 if NUMPY_MODE == True: 264 xc=xc+1 265 yc=yc+1 266 WCSCoords.append(wcs.pix2wcs(self.WCSStructure, float(xc), float(yc))) 267 else: 268 if NUMPY_MODE == True: 269 x=x+1 270 y=y+1 271 WCSCoords=wcs.pix2wcs(self.WCSStructure, float(x), float(y)) 272 273 return WCSCoords
274 275
276 - def coordsAreInImage(self, RADeg, decDeg):
277 """Returns True if the given RA, dec coordinate is within the image boundaries. 278 279 @rtype: bool 280 @return: True if coordinate within image, False if not. 281 282 """ 283 284 pixCoords=wcs.wcs2pix(self.WCSStructure, RADeg, decDeg) 285 if pixCoords[0] >= 0 and pixCoords[0] < self.header['NAXIS1'] and \ 286 pixCoords[1] >= 0 and pixCoords[1] < self.header['NAXIS2']: 287 return True 288 else: 289 return False
290 291
292 - def getRotationDeg(self):
293 """Returns the rotation angle in degrees around the axis, North through East. 294 295 @rtype: float 296 @return: rotation angle in degrees 297 298 """ 299 return self.WCSStructure.rot
300 301
302 - def isFlipped(self):
303 """Returns 1 if image is reflected around axis, otherwise returns 0. 304 305 @rtype: int 306 @return: 1 if image is flipped, 0 otherwise 307 308 """ 309 return self.WCSStructure.imflip
310 311
312 - def getPixelSizeDeg(self):
313 """Returns the pixel scale of the WCS. This is the average of the x, y pixel scales. 314 315 @rtype: float 316 @return: pixel size in decimal degrees 317 318 """ 319 320 avSize=(abs(self.WCSStructure.xinc)+abs(self.WCSStructure.yinc))/2.0 321 322 return avSize
323 324
325 - def getXPixelSizeDeg(self):
326 """Returns the pixel scale along the x-axis of the WCS in degrees. 327 328 @rtype: float 329 @return: pixel size in decimal degrees 330 331 """ 332 333 avSize=abs(self.WCSStructure.xinc) 334 335 return avSize
336 337
338 - def getYPixelSizeDeg(self):
339 """Returns the pixel scale along the y-axis of the WCS in degrees. 340 341 @rtype: float 342 @return: pixel size in decimal degrees 343 344 """ 345 346 avSize=abs(self.WCSStructure.yinc) 347 348 return avSize
349 350
351 - def getEquinox(self):
352 """Returns the equinox of the WCS. 353 354 @rtype: float 355 @return: equinox of the WCS 356 357 """ 358 return self.WCSStructure.equinox
359 360
361 - def getEpoch(self):
362 """Returns the epoch of the WCS. 363 364 @rtype: float 365 @return: epoch of the WCS 366 367 """ 368 return self.WCSStructure.epoch
369 370 371 #------------------------------------------------------------------------------------------------------------ 372 # Functions for comparing WCS objects
373 -def findWCSOverlap(wcs1, wcs2):
374 """Finds the minimum, maximum WCS coords that overlap between wcs1 and wcs2. Returns these coordinates, 375 plus the corresponding pixel coordinates for each wcs. Useful for clipping overlapping region between 376 two images. 377 378 @rtype: dictionary 379 @return: dictionary with keys 'overlapWCS' (min, max RA, dec of overlap between wcs1, wcs2) 380 'wcs1Pix', 'wcs2Pix' (pixel coords in each input WCS that correspond to 'overlapWCS' coords) 381 382 """ 383 384 mm1=wcs1.getImageMinMaxWCSCoords() 385 mm2=wcs2.getImageMinMaxWCSCoords() 386 387 overlapWCSCoords=[0.0, 0.0, 0.0, 0.0] 388 389 # Note order swapping below is essential 390 # Min RA 391 if mm1[0] - mm2[0] <= 0.0: 392 overlapWCSCoords[0]=mm2[0] 393 else: 394 overlapWCSCoords[0]=mm1[0] 395 396 # Max RA 397 if mm1[1] - mm2[1] <= 0.0: 398 overlapWCSCoords[1]=mm1[1] 399 else: 400 overlapWCSCoords[1]=mm2[1] 401 402 # Min dec. 403 if mm1[2] - mm2[2] <= 0.0: 404 overlapWCSCoords[2]=mm2[2] 405 else: 406 overlapWCSCoords[2]=mm1[2] 407 408 # Max dec. 409 if mm1[3] - mm2[3] <= 0.0: 410 overlapWCSCoords[3]=mm1[3] 411 else: 412 overlapWCSCoords[3]=mm2[3] 413 414 # Get corresponding pixel coords 415 p1Low=wcs1.wcs2pix(overlapWCSCoords[0], overlapWCSCoords[2]) 416 p1High=wcs1.wcs2pix(overlapWCSCoords[1], overlapWCSCoords[3]) 417 p1=[p1Low[0], p1High[0], p1Low[1], p1High[1]] 418 419 p2Low=wcs2.wcs2pix(overlapWCSCoords[0], overlapWCSCoords[2]) 420 p2High=wcs2.wcs2pix(overlapWCSCoords[1], overlapWCSCoords[3]) 421 p2=[p2Low[0], p2High[0], p2Low[1], p2High[1]] 422 423 return {'overlapWCS': overlapWCSCoords, 'wcs1Pix': p1, 'wcs2Pix': p2}
424 425 #------------------------------------------------------------------------------------------------------------ 426