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 - def copy(self):
82 """Copies the WCS object to a new object. 83 84 @rtype: astWCS.WCS object 85 @return: WCS object 86 87 """ 88 89 # This only sets up a new WCS object, doesn't do a deep copy 90 ret=WCS(self.headerSource, self.extensionName, self.mode) 91 92 # This fixes copy bug 93 ret.header=self.header.copy() 94 ret.updateFromHeader() 95 96 return ret
97
98 - def updateFromHeader(self):
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
111 - def getCentreWCSCoords(self):
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
126 - def getFullSizeSkyDeg(self):
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
142 - def getHalfSizeDeg(self):
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
158 - def getImageMinMaxWCSCoords(self):
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 # Get size of parent image this WCS is taken from 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
189 - def wcs2pix(self, RADeg, decDeg):
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
215 - def pix2wcs(self, x, y):
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
238 - def getRotationDeg(self):
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
247 - def isFlipped(self):
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
256 - def getPixelSizeDeg(self):
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
267 - def getEquinox(self):
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
276 - def getEpoch(self):
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 # Functions for comparing WCS objects
287 -def findWCSOverlap(wcs1, wcs2):
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 # Note order swapping below is essential 304 # Min RA 305 if mm1[0] - mm2[0] <= 0.0: 306 overlapWCSCoords[0]=mm2[0] 307 else: 308 overlapWCSCoords[0]=mm1[0] 309 310 # Max RA 311 if mm1[1] - mm2[1] <= 0.0: 312 overlapWCSCoords[1]=mm1[1] 313 else: 314 overlapWCSCoords[1]=mm2[1] 315 316 # Min dec. 317 if mm1[2] - mm2[2] <= 0.0: 318 overlapWCSCoords[2]=mm2[2] 319 else: 320 overlapWCSCoords[2]=mm1[2] 321 322 # Max dec. 323 if mm1[3] - mm2[3] <= 0.0: 324 overlapWCSCoords[3]=mm1[3] 325 else: 326 overlapWCSCoords[3]=mm2[3] 327 328 # Get corresponding pixel coords 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