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

Source Code for Module astLib.astPlots

  1  # -*- coding: utf-8 -*- 
  2  """module for producing astronomical plots 
  3   
  4  (c) 2007-2009 Matt Hilton  
  5   
  6  U{http://astlib.sourceforge.net} 
  7   
  8  This module provides the matplotlib powered ImagePlot class, which is designed to be flexible.  
  9  ImagePlots can have RA, Dec. coordinate axes, contour overlays, and have objects marked in them,  
 10  using WCS coordinates. RGB plots are supported too. 
 11   
 12  @var DEC_TICK_STEPS: Defines the possible coordinate label steps on the delination axis in 
 13  sexagesimal mode. Dictionary format: {'deg', 'int', 'unit'} 
 14  @type DEC_TICK_STEPS: dictionary list 
 15   
 16  @var RA_TICK_STEPS: Defines the possible coordinate label steps on the right ascension axis in 
 17  sexagesimal mode. Dictionary format: {'deg', 'int', 'unit'} 
 18  @type RA_TICK_STEPS: dictionary list 
 19   
 20  @var DECIMAL_TICK_STEPS: Defines the possible coordinate label steps on both coordinate axes in 
 21  decimal degrees mode. 
 22  @type DECIMAL_TICK_STEPS: list 
 23   
 24  @var DEG: Variable to stand in for the degrees symbol. 
 25  @type DEG: string 
 26   
 27  @var PRIME: Variable to stand in for the prime symbol. 
 28  @type PRIME: string 
 29   
 30  @var DOUBLE_PRIME: Variable to stand in for the double prime symbol. 
 31  @type DOUBLE_PRIME: string 
 32   
 33  """ 
 34   
 35  import math 
 36  import astImages 
 37  import astWCS 
 38  import astCoords 
 39  import numpy 
 40  import pyfits 
 41  try: 
 42      import pylab 
 43      import matplotlib.patches as patches 
 44  except: 
 45      print "WARNING: astPlots: failed to import matplotlib - some functions will not work." 
 46  import sys 
 47   
 48  DEC_TICK_STEPS=[{'deg': 1.0/60.0/60.0,  'int': 1,   'unit': "s"},  
 49                  {'deg': 5.0/60.0/60.0,  'int': 5,   'unit': "s"},  
 50                  {'deg': 10.0/60.0/60.0, 'int': 10,  'unit': "s"}, 
 51                  {'deg': 30.0/60.0/60.0, 'int': 30,  'unit': "s"}, 
 52                  {'deg': 1.0/60.0,       'int': 1,   'unit': "m"}, 
 53                  {'deg': 5.0/60.0,       'int': 5,   'unit': "m"}, 
 54                  {'deg': 15.0/60.0,      'int': 15,  'unit': "m"}, 
 55                  {'deg': 30.0/60.0,      'int': 30,  'unit': "m"},  
 56                  {'deg': 1.0,            'int': 1,    'unit': "d"}, 
 57                  {'deg': 5.0,            'int': 5,    'unit': "d"}, 
 58                  {'deg': 10.0,           'int': 10,   'unit': "d"}, 
 59                  {'deg': 30.0,           'int': 30,   'unit': "d"}] 
 60   
 61  RA_TICK_STEPS=[ {'deg': (1.0/60.0/60.0/24.0)*360.0,  'int': 1,   'unit': "s"},  
 62                  {'deg': (5.0/60.0/60.0/24.0)*360.0,  'int': 5,   'unit': "s"},  
 63                  {'deg': (10.0/60.0/60.0/24.0)*360.0, 'int': 10,  'unit': "s"}, 
 64                  {'deg': (30.0/60.0/60.0/24.0)*360.0, 'int': 30,  'unit': "s"}, 
 65                  {'deg': (1.0/60.0/24.0)*360.0,       'int': 1,   'unit': "m"}, 
 66                  {'deg': (5.0/60.0/24.0)*360.0,       'int': 5,   'unit': "m"}, 
 67                  {'deg': (10.0/60.0/24.0)*360.0,      'int': 15,  'unit': "m"}, 
 68                  {'deg': (30.0/60.0/24.0)*360.0,      'int': 30,  'unit': "m"},  
 69                  {'deg': (1.0/24.0)*360.0,            'int': 1,   'unit': "h"}, 
 70                  {'deg': (3.0/24.0)*360.0,            'int': 3,   'unit': "h"}, 
 71                  {'deg': (6.0/24.0)*360.0,            'int': 6,   'unit': "h"}, 
 72                  {'deg': (12.0/24.0)*360.0,           'int': 12,  'unit': "h"}] 
 73   
 74  DECIMAL_TICK_STEPS=[0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 90.0] 
 75   
 76  DEG = u"\N{DEGREE SIGN}" 
 77  PRIME = "\'"            #u"\N{PRIME}" ought to work, but not with my fonts 
 78  DOUBLE_PRIME = "\""     #u"\N{DOUBLE PRIME}" 
 79   
 80  #--------------------------------------------------------------------------------------------------- 
81 -class ImagePlot:
82 """This class describes a matplotlib image plot containing an astronomical image with an 83 associated WCS. 84 85 Objects within the image boundaries can be marked by passing their WCS coordinates to 86 L{ImagePlot.addPlotObjects}. 87 88 Other images can be overlaid using L{ImagePlot.addContourOverlay}. 89 90 For images rotated with North at the top, East at the left (as can be done using 91 L{astImages.clipRotatedImageSectionWCS}), WCS coordinate axes can be plotted, with tic marks 92 set appropriately for the image size (note that this may not currently be 100% reliable for 93 sexagesimal coordinates). Otherwise, a compass can be plotted showing the directions of North 94 and East in the image. 95 96 RGB images are also supported. 97 98 The plot can of course be tweaked further after creation using matplotlib/pylab commands. 99 100 """
101 - def __init__(self, imageData, imageWCS, axes = [0.1,0.1,0.8,0.8], \ 102 cutLevels = ["smart", 99.5], colorMapName = "gray", title = None, axesLabels = "decimal", \ 103 minorLabels = False, axesFontFamily="serif", axesFontSize=12.0, colorBar = False):
104 """Makes an ImagePlot from the given image array and astWCS. For coordinate axes to work, the 105 image and WCS should have been rotated such that East is at the left, North is at the top 106 (see e.g. L{astImages.clipRotatedImageSectionWCS}). 107 108 If imageData is given as a list in the format [r, g, b], an color RGB plot will be made. However, 109 in this case the cutLevels must be specified manually for each component as a list - 110 i.e. cutLevels = [[r min, r max], [g min, g max], [b min, b max]]. In this case of course, the 111 colorMap will be ignored. All r, g, b image arrays must have the same dimensions. 112 113 Set axesLabels = None to make a plot without coordinate axes plotted. 114 115 The axes can be marked in either sexagesimal or decimal celestial coordinates: the appropriate 116 axis scales will be determined automatically from the size of the image array and associated 117 WCS. 118 119 @type imageData: numpy array or list 120 @param imageData: image data array or list of numpy arrays [r, g, b] 121 @type imageWCS: astWCS.WCS 122 @param imageWCS: astWCS.WCS object 123 @type axes: list 124 @param axes: specifies where in the current figure to draw the finder chart (see pylab.axes) 125 @type cutLevels: list 126 @param cutLevels: sets the image scaling - available options: 127 - pixel values: cutLevels=[low value, high value]. 128 - histogram equalisation: cutLevels=["histEq", number of bins ( e.g. 1024)] 129 - relative: cutLevels=["relative", cut per cent level (e.g. 99.5)] 130 - smart: cutLevels=["smart", cut per cent level (e.g. 99.5)] 131 ["smart", 99.5] seems to provide good scaling over a range of different images. 132 Note that for RGB images, cut levels must be specified manually i.e. as a list: 133 [[r min, rmax], [g min, g max], [b min, b max]] 134 @type colorMapName: string 135 @param colorMapName: name of a standard matplotlib colormap, e.g. "hot", "cool", "gray" 136 etc. (do "help(pylab.colormaps)" in the Python interpreter to see available options) 137 @type title: string 138 @param title: optional title for the plot 139 @type axesLabels: string 140 @param axesLabels: either "sexagesimal" (for H:M:S, D:M:S), "decimal" (for decimal degrees) 141 or None (for no coordinate axes labels) 142 @type minorLabels: bool 143 @param minorLabels: if set to True, add additional labels between coordinate labels 144 @type axesFontFamily: string 145 @param axesFontFamily: matplotlib fontfamily, e.g. 'serif', 'sans-serif' etc. 146 @type axesFontSize: float 147 @param axesFontSize: font size of axes labels and titles (in points) 148 @type colorBar: bool 149 @param colorBar: if True, plot a vertical color bar at the side of the image indicating the intensity 150 scale. 151 152 """ 153 154 self.RADeg, self.decDeg=imageWCS.getCentreWCSCoords() 155 self.wcs=imageWCS 156 157 # Handle case where imageData is [r, g, b] 158 if type(imageData) == list: 159 if len(imageData) == 3: 160 if len(cutLevels) == 3: 161 r=astImages.normalise(imageData[0], cutLevels[0]) 162 g=astImages.normalise(imageData[1], cutLevels[1]) 163 b=astImages.normalise(imageData[2], cutLevels[2]) 164 rgb=numpy.array([r.transpose(), g.transpose(), b.transpose()]) 165 rgb=rgb.transpose() 166 self.data=rgb 167 self.rgbImage=True 168 else: 169 raise Exception, "tried to create a RGB array, but cutLevels is not a list of 3 lists" 170 171 else: 172 raise Exception, "tried to create a RGB array but imageData is not a list of 3 arrays" 173 else: 174 self.data=imageData 175 self.rgbImage=False 176 177 self.axes=pylab.axes(axes) 178 self.cutLevels=cutLevels 179 self.colorMapName=colorMapName 180 self.title=title 181 self.axesLabels=axesLabels 182 self.minorLabels=minorLabels 183 self.colorBar=colorBar 184 self.axesFontSize=axesFontSize 185 self.axesFontFamily=axesFontFamily 186 187 if self.axesLabels != None: 188 self.calcWCSAxisLabels(axesLabels = self.axesLabels, minorLabels = self.minorLabels) 189 190 # this list stores objects to overplot, add to it using addPlotObjects() 191 self.plotObjects=[] 192 193 # this list stores image data to overlay as contours, add to it using addContourOverlay() 194 self.contourOverlays=[] 195 196 self.draw()
197 198
199 - def draw(self):
200 """Redraws the ImagePlot - this should be called after adding plot objects, compass, contours 201 etc. 202 203 """ 204 205 pylab.axes(self.axes) 206 pylab.cla() 207 208 if self.title != None: 209 pylab.title(self.title) 210 try: 211 colorMap=pylab.cm.get_cmap(self.colorMapName) 212 except AssertionError: 213 raise Exception, self.colorMapName+"is not a defined matplotlib colormap." 214 215 if self.rgbImage == False: 216 self.cutImage=astImages.intensityCutImage(self.data, self.cutLevels) 217 if self.cutLevels[0]=="histEq": 218 pylab.imshow(self.cutImage['image'], interpolation="bilinear", origin='lower', cmap=colorMap) 219 else: 220 pylab.imshow(self.cutImage['image'], interpolation="bilinear", norm=self.cutImage['norm'], \ 221 origin='lower', cmap=colorMap) 222 else: 223 pylab.imshow(self.data, interpolation="bilinear", origin='lower') 224 225 if self.colorBar == True: 226 pylab.colorbar(shrink=0.8) 227 228 for c in self.contourOverlays: 229 pylab.contour(c['contourData']['scaledImage'], c['contourData']['contourLevels'], 230 colors=c['color'], linewidths=c['width']) 231 232 for p in self.plotObjects: 233 for x, y, l in zip(p['x'], p['y'], p['objLabels']): 234 if p['symbol'] == "circle": 235 c=patches.Circle((x, y), radius=p['sizePix']/2.0, fill=False, edgecolor=p['color'], 236 linewidth=p['width']) 237 self.axes.add_patch(c) 238 elif p['symbol'] == "box": 239 c=patches.Rectangle((x-p['sizePix']/2, y-p['sizePix']/2), p['sizePix'], p['sizePix'], 240 fill=False, edgecolor=p['color'], linewidth=p['width']) 241 self.axes.add_patch(c) 242 elif p['symbol'] == "cross": 243 pylab.plot([x-p['sizePix']/2, x+p['sizePix']/2], [y, y], linestyle='-', 244 linewidth=p['width'], color= p['color']) 245 pylab.plot([x, x], [y-p['sizePix']/2, y+p['sizePix']/2], linestyle='-', 246 linewidth=p['width'], color= p['color']) 247 if l != None: 248 pylab.text(x, y+p['sizePix']/1.5, l, horizontalalignment='center', \ 249 fontsize=p['objLabelSize'], color=p['color']) 250 251 if p['symbol'] == "compass": 252 x=p['x'][0] 253 y=p['y'][0] 254 ra=p['RA'][0] 255 dec=p['dec'][0] 256 northPoint=dec+p['sizeArcSec']/3600.0 257 eastPoint=ra+p['sizeArcSec']/3600.0 258 sizePix=(p['sizeArcSec']/3600.0)/self.wcs.getPixelSizeDeg() 259 northPix=self.wcs.wcs2pix(ra, northPoint) 260 eastPix=self.wcs.wcs2pix(eastPoint, dec) 261 edx=eastPix[0]-x 262 edy=eastPix[1]-y 263 ndx=northPix[0]-x 264 ndy=northPix[1]-y 265 nArrow=patches.Arrow(x, y, ndx, ndy, edgecolor=p['color'], facecolor=p['color'], width=p['width']) 266 eArrow=patches.Arrow(x, y, edx, edy, edgecolor=p['color'], facecolor=p['color'], width=p['width']) 267 self.axes.add_patch(nArrow) 268 self.axes.add_patch(eArrow) 269 pylab.text(x+ndx+ndx*0.2, y+ndy+ndy*0.2, "N", horizontalalignment='center', 270 verticalalignment='center', fontsize=p['objLabelSize'], color=p['color']) 271 pylab.text(x+edx+edx*0.2, y+edy+edy*0.2, "E", horizontalalignment='center', 272 verticalalignment='center', fontsize=p['objLabelSize'], color=p['color']) 273 274 if self.axesLabels != None: 275 pylab.xticks(self.ticsRA[0], self.ticsRA[1], weight='normal', family=self.axesFontFamily, \ 276 fontsize=self.axesFontSize) 277 pylab.yticks(self.ticsDec[0], self.ticsDec[1], weight='normal', family=self.axesFontFamily, \ 278 fontsize=self.axesFontSize) 279 pylab.xlabel(self.RAAxisLabel, family=self.axesFontFamily, fontsize=self.axesFontSize) 280 pylab.ylabel(self.decAxisLabel, family=self.axesFontFamily, fontsize=self.axesFontSize) 281 else: 282 pylab.xticks([], []) 283 pylab.yticks([], []) 284 pylab.xlabel("") 285 pylab.ylabel("") 286 287 pylab.xlim(0, self.data.shape[1]-1) 288 pylab.ylim(0, self.data.shape[0]-1)
289 290
291 - def addContourOverlay(self, contourImageData, contourWCS, label, levels = ['linear', 'min', 'max', 5], \ 292 width = 1, color = "white", smooth = 0):
293 """Adds image data to the ImagePlot as a contour overlay. The contours will be plotted when 294 ImagePlot.draw() is next called. The contours can be removed using L{removeContourOverlay} 295 If a contour overlay already exists with this label, it will be replaced. 296 297 @type contourImageData: numpy array 298 @param contourImageData: image data array from which contours are to be generated 299 @type contourWCS: astWCS.WCS 300 @param contourWCS: astWCS.WCS object for the image to be contoured 301 @type label: string 302 @param label: identifying label for this set of contours 303 @type levels: list 304 @param levels: sets the contour levels - available options: 305 - values: contourLevels=[list of values specifying each level] 306 - linear spacing: contourLevels=['linear', min level value, max level value, number 307 of levels] 308 - log spacing: contourLevels=['log', min level value, max level value, number of 309 levels] 310 @type width: int 311 @param width: width of the overlaid contours 312 @type color: string 313 @param color: color of the overlaid contours, specified by the name of a standard 314 matplotlib color, e.g., "black", "white", "cyan" 315 etc. (do "help(pylab.colors)" in the Python interpreter to see available options) 316 @type smooth: float 317 @param smooth: standard deviation (in pixels) of Gaussian filter for 318 pre-smoothing of contour image data (set to 0 for no smoothing) 319 320 """ 321 322 if self.rgbImage == True: 323 backgroundData=self.data[:,:,0] 324 else: 325 backgroundData=self.data 326 contourData=astImages.generateContourOverlay(backgroundData, self.wcs, contourImageData, \ 327 contourWCS, levels, smooth) 328 329 alreadyGot=False 330 for c in self.contourOverlays: 331 if c['label'] == label: 332 c['contourData']=contourData 333 c['label']=label 334 c['color']=color 335 c['width']=width 336 alreadyGot=True 337 338 if alreadyGot == False: 339 self.contourOverlays.append({'contourData': contourData, 'label': label, 'color': color, \ 340 'width': width})
341 342
343 - def removeContourOverlay(self, label):
344 """Removes the contourOverlay from the ImagePlot corresponding to the label. The plot must be redrawn 345 for the change to take effect. 346 347 @type label: string 348 @param label: label for contour overlay in ImagePlot.contourOverlays to be removed 349 350 """ 351 352 index=0 353 for p in self.contourOverlays: 354 if p['label'] == label: 355 self.plotObjects.remove(self.plotObjects[index]) 356 index=index+1
357 358
359 - def addPlotObjects(self, objRAs, objDecs, label, symbol="circle", size=4.0, width=1.0, color="yellow", 360 objLabels = None, objLabelSize = 12.0):
361 """Add objects with RA, dec coords objRAs, objDecs to the ImagePlot. The objects will be plotted 362 when ImagePlot.draw() is next called; only objects that fall within the image boundaries will be 363 plotted. 364 365 symbol specifies the type of symbol with which to mark the object in the image. The following 366 values are allowed: 367 - "circle" 368 - "box" 369 - "cross" 370 371 size specifies the diameter in arcsec of the symbol (if plotSymbol == "circle"), or the width 372 of the box in arcsec (if plotSymbol == "box") 373 374 width specifies the thickness of the symbol lines in pixels 375 376 color can be any valid matplotlib color (e.g. "red", "green", etc.) 377 378 The objects can be removed from the plot by using removePlotObjects(), and then calling 379 draw(). If the ImagePlot already has a set of plotObjects with the same label, they will be 380 replaced. 381 382 @type objRAs: numpy array or list 383 @param objRAs: object RA coords in decimal degrees 384 @type objDecs: numpy array or list 385 @param objDecs: corresponding object Dec. coords in decimal degrees 386 @type label: string 387 @param label: identifying label for this set of objects 388 @type symbol: string 389 @param symbol: either "circle" or "box" 390 @type size: float 391 @param size: size of symbols to plot (radius in arcsec, or width of box) 392 @type width: float 393 @param width: width of symbols in pixels 394 @type color: string 395 @param color: any valid matplotlib color string, e.g. "red", "green" etc. 396 @type objLabels: list 397 @param objLabels: text labels to plot next to objects in figure 398 @type objLabelSize: float 399 @param objLabelSize: size of font used for object labels (in points) 400 401 """ 402 403 pixCoords=self.wcs.wcs2pix(objRAs, objDecs) 404 405 xMax=self.data.shape[1] 406 yMax=self.data.shape[0] 407 408 if objLabels == None: 409 objLabels=[None]*len(objRAs) 410 411 xInPlot=[] 412 yInPlot=[] 413 RAInPlot=[] 414 decInPlot=[] 415 labelInPlot=[] 416 for p, r, d, l in zip(pixCoords, objRAs, objDecs, objLabels): 417 if p[0] >= 0 and p[0] < xMax and p[1] >= 0 and p[1] < yMax: 418 xInPlot.append(p[0]) 419 yInPlot.append(p[1]) 420 RAInPlot.append(r) 421 decInPlot.append(d) 422 labelInPlot.append(l) 423 424 xInPlot=numpy.array(xInPlot) 425 yInPlot=numpy.array(yInPlot) 426 RAInPlot=numpy.array(RAInPlot) 427 decInPlot=numpy.array(decInPlot) 428 429 # Size of symbols in pixels in plot - converted from arcsec 430 sizePix=(size/3600.0)/self.wcs.getPixelSizeDeg() 431 432 alreadyGot=False 433 for p in self.plotObjects: 434 if p['label'] == label: 435 p['x']=xInPlot 436 p['y']=yInPlot 437 p['RA']=RAInPlot 438 p['dec']=decInPlot 439 p['label']=label 440 p['objLabels']=objLabels 441 p['symbol']=symbol 442 p['sizePix']=sizePix 443 p['sizeArcSec']=size 444 p['width']=width 445 p['color']=color 446 p['objLabelSize']=objLabelSize 447 alreadyGot=True 448 449 if alreadyGot == False: 450 self.plotObjects.append({'x': xInPlot, 'y': yInPlot, 'RA': RAInPlot, 'dec': decInPlot, 451 'label': label, 'objLabels': labelInPlot, 'symbol': symbol, 452 'sizePix': sizePix, 'width': width, 'color': color, 453 'objLabelSize': objLabelSize, 'sizeArcSec': size})
454 455
456 - def removePlotObjects(self, label):
457 """Removes the plotObjects from the ImagePlot corresponding to the label. The plot must be redrawn 458 for the change to take effect. 459 460 @type label: string 461 @param label: label for set of objects in ImagePlot.plotObjects to be removed 462 463 """ 464 465 index=0 466 for p in self.plotObjects: 467 if p['label'] == label: 468 self.plotObjects.remove(self.plotObjects[index]) 469 index=index+1
470 471
472 - def addCompass(self, location, sizeArcSec, color = "white", fontSize = 12, \ 473 width = 20.0):
474 """Adds a compass to the ImagePlot at the given location ('N', 'NE', 'E', 'SE', 'S', 475 'SW', 'W', or 'NW'). Note these aren't directions on the WCS coordinate grid, they are 476 relative positions on the plot - so N is top centre, NE is top right, SW is bottom right etc.. 477 Alternatively, pixel coordinates (x, y) in the image can be given. 478 479 @type location: string or tuple 480 @param location: location in the plot where the compass is drawn: 481 - string: N, NE, E, SE, S, SW, W or NW 482 - tuple: (x, y) 483 @type sizeArcSec: float 484 @param sizeArcSec: length of the compass arrows on the plot in arc seconds 485 @type color: string 486 @param color: any valid matplotlib color string 487 @type fontSize: float 488 @param fontSize: size of font used to label N and E, in points 489 @type width: float 490 @param width: width of arrows used to mark compass 491 492 """ 493 494 # Work out where the compass is going in WCS coords from the relative location given 495 # Draw the compass 3 * the given size in arcsec from that edge of the plot 496 if type(location) == str: 497 RADeg, decDeg=self.wcs.getCentreWCSCoords() 498 x, y=self.wcs.wcs2pix(RADeg, decDeg) 499 halfWidthRADeg, halfHeightDecDeg=self.wcs.getHalfSizeDeg() 500 compassDistRADeg=halfWidthRADeg-3.0*sizeArcSec/3600.0 501 compassDistDecDeg=halfHeightDecDeg-3.0*sizeArcSec/3600.0 502 if self.wcs.isFlipped() == True: 503 compassDistRADeg=compassDistRADeg*-1 504 foundLocation=False 505 if location.find("N") != -1: 506 y=y+compassDistDecDeg/self.wcs.getPixelSizeDeg() 507 foundLocation=True 508 if location.find("S") != -1: 509 y=y-compassDistDecDeg/self.wcs.getPixelSizeDeg() 510 foundLocation=True 511 if location.find("E") != -1: 512 x=x-compassDistRADeg/self.wcs.getPixelSizeDeg() 513 foundLocation=True 514 if location.find("W") != -1: 515 x=x+compassDistRADeg/self.wcs.getPixelSizeDeg() 516 foundLocation=True 517 if foundLocation == False: 518 raise Exception, "didn't understand location string for compass (should be e.g. N, S, E, W)." 519 elif type(location) == tuple: 520 x, y=location 521 else: 522 raise Exception, "didn't understand location for compass - should be string or tuple." 523 RADeg, decDeg=self.wcs.pix2wcs(x, y) 524 525 # Size of compass arrows pixels in plot - converted from arcsec 526 sizePix=(sizeArcSec/3600.0)/self.wcs.getPixelSizeDeg() 527 528 alreadyGot=False 529 for p in self.plotObjects: 530 if p['label'] == "compass": 531 p['x']=[x] 532 p['y']=[y] 533 p['RA']=[RADeg] 534 p['dec']=[decDeg] 535 p['label']="compass" 536 p['objLabels']=[None] 537 p['symbol']="compass" 538 p['sizePix']=sizePix 539 p['sizeArcSec']=sizeArcSec 540 p['width']=width 541 p['color']=color 542 p['objLabelSize']=fontSize 543 alreadyGot=True 544 545 if alreadyGot == False: 546 self.plotObjects.append({'x': [x], 'y': [y], 'RA': [RADeg], 'dec': [decDeg], 547 'label': "compass", 'objLabels': [None], 'symbol': "compass", 548 'sizePix': sizePix, 'width': width, 'color': color, 549 'objLabelSize': fontSize, 'sizeArcSec': sizeArcSec})
550 551
552 - def calcWCSAxisLabels(self, axesLabels = "decimal", minorLabels = False):
553 """ 554 This function calculates the positions of coordinate labels for the RA and Dec axes of the 555 ImagePlot. The ImagePlot must be redrawn for changes to be applied. 556 557 @type axesLabels: string 558 @param axesLabels: either "sexagesimal" (for H:M:S, D:M:S), "decimal" (for decimal degrees), 559 or None for no coordinate axes labels 560 @type minorLabels: bool 561 @param minorLabels: if set to True, add additional labels between coordinate labels 562 563 """ 564 565 self.axesLabels=axesLabels 566 self.minorLabels=minorLabels 567 568 # Label equinox on axes 569 equinox=self.wcs.getEquinox() 570 if equinox<1984: 571 equinoxLabel="B"+str(int(equinox)) 572 else: 573 equinoxLabel="J"+str(int(equinox)) 574 575 # Work out axes labels 576 topLeft=self.wcs.pix2wcs(0,0) 577 bottomRight=self.wcs.pix2wcs(self.data.shape[1], self.data.shape[0]) 578 579 # Pick appropriate scale for tics 580 ticLabelListRA=[] 581 ticLocListRA=[] 582 ticLabelListDec=[] 583 ticLocListDec=[] 584 585 # Count up in RA sec from bottom right 586 hmsRALeft=astCoords.decimal2hms(topLeft[0], ":") 587 hmsRARight=astCoords.decimal2hms(bottomRight[0], ":") 588 if topLeft[0] > bottomRight[0]: 589 raDegDiff=abs(topLeft[0]-bottomRight[0]) 590 else: 591 # handle wraparound 592 raDegDiff=(360.0-bottomRight[0])+topLeft[0] 593 tickMarkSteps=getTickSteps(raDegDiff, "ra") 594 bitsRight=hmsRARight.split(":") 595 bitsLeft=hmsRALeft.split(":") 596 raMajorTickStep=tickMarkSteps['major'] 597 raMinorTickStep=tickMarkSteps['minor'] 598 raUnmarkedTickStep=tickMarkSteps['unlabelled'] 599 600 # Count up in dec arcsec from bottom right 601 dmsDecTop=astCoords.decimal2dms(topLeft[1], ":") 602 dmsDecBottom=astCoords.decimal2dms(bottomRight[1], ":") 603 decDegDiff=abs(topLeft[1]-bottomRight[1]) 604 tickMarkSteps=getTickSteps(decDegDiff, "dec") 605 bitsTop=dmsDecTop.split(":") 606 bitsBottom=dmsDecBottom.split(":") 607 decMajorTickStep=tickMarkSteps['major'] 608 decMinorTickStep=tickMarkSteps['minor'] 609 decUnmarkedTickStep=tickMarkSteps['unlabelled'] 610 611 if self.axesLabels == "sexagesimal": 612 613 xAxisLabel="R.A. ("+equinoxLabel+")" 614 yAxisLabel="Dec. ("+equinoxLabel+")" 615 616 hRange=range(int(bitsRight[0])-2, int(bitsLeft[0])+2) 617 # handle wrap around 618 if bitsLeft[0] < bitsRight[0]: 619 hRange=range(int(bitsRight[0]), 24)+range(int(bitsLeft[0]),int(bitsLeft[0])+1) 620 mRange=range(int(bitsRight[1]), int(bitsLeft[1])+2) 621 if len(hRange)>1: 622 mRange=range(0, 60) 623 sRange=range(0, 60) 624 for h in hRange: 625 hString=str(h) 626 if abs(h)<10: 627 hString="0"+hString 628 for m in mRange: 629 mString=str(m) 630 if m<10: 631 mString="0"+str(m) 632 for s in sRange: 633 sString=str(s)+".000" 634 if s<10: 635 sString="0"+str(s)+".000" 636 hmsMark=hString+":"+mString+":"+sString 637 degMark=astCoords.hms2decimal(hmsMark, ":") 638 pixMark=self.wcs.wcs2pix(degMark, bottomRight[1]) 639 if pixMark[0] > 0 and pixMark[0] < self.data.shape[1]: 640 addedLabel=False 641 if eval(raMajorTickStep['unit']+" % "+str(raMajorTickStep['int'])) == 0: 642 if raMajorTickStep['unit'] == "h" and m == 0 and s == 0 : 643 label=hmsMark.split(":")[0]+".0" 644 ticLabelListRA.append(label) 645 ticLocListRA.append(int(round(pixMark[0]))) 646 addedLabel=True 647 elif raMajorTickStep['unit'] == "m" and s == 0: 648 label=hmsMark.split(":")[0]+":"+hmsMark.split(":")[1]+".0" 649 ticLabelListRA.append(label) 650 ticLocListRA.append(int(round(pixMark[0]))) 651 addedLabel=True 652 elif raMajorTickStep['unit'] == "s": 653 label=hmsMark.split(":")[0]+":"+hmsMark.split(":")[1]+":"+hmsMark.split(":")[2][:-4]+".0" 654 ticLabelListRA.append(label) 655 ticLocListRA.append(int(round(pixMark[0]))) 656 addedLabel=True 657 658 if self.minorLabels == True: 659 if addedLabel == False and eval(raMinorTickStep['unit']+" % "+str(raMinorTickStep['int'])) == 0: 660 if raMinorTickStep['unit'] == "h" and m == 0 and s == 0 : 661 label="" # No minor tick marks if we're in hours 662 ticLabelListRA.append(label) 663 ticLocListRA.append(int(round(pixMark[0]))) 664 addedLabel=True 665 elif raMinorTickStep['unit'] == "m" and s == 0: 666 label=hmsMark.split(":")[1]+".0" 667 ticLabelListRA.append(label) 668 ticLocListRA.append(int(round(pixMark[0]))) 669 addedLabel=True 670 elif raMinorTickStep['unit'] == "s": 671 label=hmsMark.split(":")[2][:-4]+".0" 672 ticLabelListRA.append(label) 673 ticLocListRA.append(int(round(pixMark[0]))) 674 addedLabel=True 675 676 if addedLabel == False and eval(raUnmarkedTickStep['unit']+" % "+str(raUnmarkedTickStep['int'])) == 0: 677 ticLabelListRA.append("") 678 ticLocListRA.append(int(round(pixMark[0]))) 679 680 # Dec. 681 dRange=range(int(bitsBottom[0])-2, int(bitsTop[0])+2) 682 if 0 in dRange: 683 dRange.insert(dRange.index(0), 0) 684 minusZero=True 685 mRange=range(int(bitsBottom[1]), int(bitsTop[1])+2) 686 if len(dRange)>1: 687 mRange=range(0, 60) 688 sRange=range(0, 60) 689 for d in dRange: 690 if d<0: 691 dString=str(d) 692 elif d==0: 693 if minusZero == True: 694 dString="-"+str(d) 695 minusZero=False 696 else: 697 dString="+"+str(d) 698 else: 699 dString="+"+str(d) 700 if abs(d)<10: 701 dString=dString[:1]+"0"+dString[1:] 702 703 for m in mRange: 704 mString=str(m) 705 if m<10: 706 mString="0"+str(m) 707 for s in sRange: 708 sString=str(s)+".00" 709 if s<10: 710 sString="0"+str(s)+".00" 711 dmsMark=dString+":"+mString+":"+sString 712 degMark=astCoords.dms2decimal(dmsMark, ":") 713 pixMark=self.wcs.wcs2pix(bottomRight[0], degMark) 714 if pixMark[1] > 0 and pixMark[1] < self.data.shape[0]: 715 addedLabel=False 716 if eval(decMajorTickStep['unit']+" % "+str(decMajorTickStep['int'])) == 0: 717 if decMajorTickStep['unit'] == "d" and m == 0 and s == 0 : 718 if dmsMark.split(":")[0] == "-00": 719 continue 720 else: 721 label=dmsMark.split(":")[0]+DEG+".0" 722 ticLabelListDec.append(label) 723 ticLocListDec.append(int(round(pixMark[1]))) 724 addedLabel=True 725 elif decMajorTickStep['unit'] == "m" and s == 0: 726 if dmsMark.split(":")[0] == "-00" and dmsMark.split(":")[1] == "00": 727 continue 728 else: 729 label=dmsMark.split(":")[0]+DEG+dmsMark.split(":")[1]+PRIME+".0" 730 ticLabelListDec.append(label) 731 ticLocListDec.append(int(round(pixMark[1]))) 732 addedLabel=True 733 elif decMajorTickStep['unit'] == "s": 734 label=dmsMark.split(":")[0]+DEG+dmsMark.split(":")[1]+PRIME+dmsMark.split(":")[2][:-3]+DOUBLE_PRIME+".0" 735 ticLabelListDec.append(label) 736 ticLocListDec.append(int(round(pixMark[1]))) 737 addedLabel=True 738 739 if self.minorLabels == True: 740 if addedLabel == False and eval(decMinorTickStep['unit']+" % "+str(decMinorTickStep['int'])) == 0: 741 if decMinorTickStep['unit'] == "d" and m == 0 and s == 0 : 742 label="" # No minor tick marks if we're in degrees 743 ticLabelListDec.append(label) 744 ticLocListDec.append(int(round(pixMark[1]))) 745 addedLabel=True 746 elif decMinorTickStep['unit'] == "m" and s == 0: 747 label=dmsMark.split(":")[1]+PRIME+".0" 748 ticLabelListDec.append(label) 749 ticLocListDec.append(int(round(pixMark[1]))) 750 addedLabel=True 751 elif decMinorTickStep['unit'] == "s": 752 label=dmsMark.split(":")[2][:-3]+DOUBLE_PRIME+".0" 753 ticLabelListDec.append(label) 754 ticLocListDec.append(int(round(pixMark[1]))) 755 addedLabel=True 756 757 if addedLabel == False and eval(decUnmarkedTickStep['unit']+" % "+str(decUnmarkedTickStep['int'])) == 0: 758 ticLabelListDec.append("") 759 ticLocListDec.append(int(round(pixMark[1]))) 760 761 elif self.axesLabels == "decimal": 762 763 xAxisLabel="R.A. Degrees ("+equinoxLabel+")" 764 yAxisLabel="Dec. Degrees ("+equinoxLabel+")" 765 766 if topLeft[0] > bottomRight[0]: 767 raDegDiff=abs(topLeft[0]-bottomRight[0]) 768 else: 769 # handle wraparound 770 raDegDiff=(360.0-bottomRight[0])+topLeft[0] 771 tickMarkSteps=getTickSteps(raDegDiff, None, mode = "decimal") 772 raMajorTickStep=tickMarkSteps['major'] 773 raMinorTickStep=tickMarkSteps['minor'] 774 raUnmarkedTickStep=tickMarkSteps['unlabelled'] 775 raMajorRatio=raMajorTickStep/raUnmarkedTickStep 776 raMinorRatio=raMinorTickStep/raUnmarkedTickStep 777 raMajorFormat="%."+str(len(str(raMajorTickStep)[str(raMajorTickStep).find(".")+1:]))+"f" 778 raMinorFormat="%."+str(len(str(raMinorTickStep)[str(raMinorTickStep).find(".")+1:]))+"f" 779 780 # Note we're using same tick marks steps for both RA and dec (looks a bit neater) 781 decDegDiff=abs(topLeft[1]-bottomRight[1]) 782 decMajorTickStep=tickMarkSteps['major'] 783 decMinorTickStep=tickMarkSteps['minor'] 784 decUnmarkedTickStep=tickMarkSteps['unlabelled'] 785 decMajorRatio=decMajorTickStep/decUnmarkedTickStep 786 decMinorRatio=decMinorTickStep/decUnmarkedTickStep 787 decMajorFormat="%."+str(len(str(decMajorTickStep)[str(decMajorTickStep).find(".")+1:]))+"f" 788 decMinorFormat="%."+str(len(str(decMinorTickStep)[str(decMinorTickStep).find(".")+1:]))+"f" 789 790 # don't want to have different labels at different precision 791 if self.minorLabels == True: 792 raMajorFormat=raMinorFormat 793 decMajorFormat=decMinorFormat 794 795 # Need to check this out at RA wrap around (probably be okay but slow) 796 raMin=int(topLeft[0])-2 797 raMax=int(bottomRight[0])+2 798 raSteps=int((raMax-raMin)/raUnmarkedTickStep) 799 800 decMin=int(bottomRight[1])-2 801 decMax=int(topLeft[1])+2 802 decSteps=int((decMax-decMin)/decUnmarkedTickStep) 803 804 for i in range(raSteps): 805 degMark=raMin+i*raUnmarkedTickStep 806 if degMark < 360: 807 pixMark=self.wcs.wcs2pix(degMark, bottomRight[1]) 808 if pixMark[0] > 0 and pixMark[0] < self.data.shape[1]: 809 addedLabel=False 810 if i % raMajorRatio == 0: 811 ticLabelListRA.append(raMajorFormat % degMark) 812 ticLocListRA.append(int(round(pixMark[0]))) 813 addedLabel=True 814 815 if self.minorLabels == True: 816 if addedLabel == False and i % raMinorRatio == 0: 817 ticLabelListRA.append(raMinorFormat % degMark) 818 ticLocListRA.append(int(round(pixMark[0]))) 819 addedLabel=True 820 821 if addedLabel == False: 822 ticLabelListRA.append("") 823 ticLocListRA.append(int(round(pixMark[0]))) 824 825 for i in range(decSteps): 826 degMark=decMin+i*decUnmarkedTickStep 827 pixMark=self.wcs.wcs2pix(bottomRight[0], degMark) 828 if pixMark[1] > 0 and pixMark[1] < self.data.shape[0]: 829 addedLabel=False 830 if i % decMajorRatio == 0: 831 ticLabelListDec.append(decMajorFormat % degMark) 832 ticLocListDec.append(int(round(pixMark[1]))) 833 addedLabel=True 834 835 if self.minorLabels == True: 836 if addedLabel == False and i % decMinorRatio == 0: 837 ticLabelListDec.append(decMinorFormat % degMark) 838 ticLocListDec.append(int(round(pixMark[1]))) 839 addedLabel=True 840 841 if addedLabel == False: 842 ticLabelListDec.append("") 843 ticLocListDec.append(int(round(pixMark[1]))) 844 845 self.ticsRA=[ticLocListRA, ticLabelListRA] 846 self.ticsDec=[ticLocListDec, ticLabelListDec] 847 self.RAAxisLabel=xAxisLabel 848 self.decAxisLabel=yAxisLabel
849 850
851 - def save(self, fileName):
852 """Saves the ImagePlot in any format that matplotlib can understand, as determined from the 853 fileName extension. 854 855 @type fileName: string 856 @param fileName: path where plot will be written 857 858 """ 859 860 pylab.draw() 861 pylab.savefig(fileName)
862 863 864 #---------------------------------------------------------------------------------------------------
865 -def getTickSteps(plotSizeDeg, axis, mode = "sexagesimal"):
866 """Chooses the appropriate WCS coordinate tick steps for the given axis of a plot of given size. 867 868 @type plotSizeDeg: float 869 @param plotSizeDeg: size of plot in decimal degrees 870 @type axis: string 871 @param axis: either "ra" or "dec" 872 @type mode: string 873 @param mode: either "sexagesimal" (for H:M:S, D:M:S) or "decimal" (for decimal degrees) 874 @rtype: dictionary 875 @return: tick step sizes for major, minor and unlabelled plot ticks, in format {'major', 'minor', 876 'unmarked'} 877 878 @note: axis is ignored is mode = "decimal" 879 880 """ 881 882 # Aim for 5 major tick marks on a plot 883 884 if mode == "sexagesimal": 885 886 if axis == "ra": 887 matchIndex = 0 888 for i in range(len(RA_TICK_STEPS)): 889 if plotSizeDeg/3.0 > RA_TICK_STEPS[i]['deg']: 890 matchIndex = i 891 # So don't wrap around the unmarked ticks 892 if matchIndex<2: 893 matchIndex=2 894 895 return {'major': RA_TICK_STEPS[matchIndex], 'minor': RA_TICK_STEPS[matchIndex-1], 'unlabelled': RA_TICK_STEPS[matchIndex-2]} 896 897 elif axis == "dec": 898 matchIndex = 0 899 for i in range(len(DEC_TICK_STEPS)): 900 if plotSizeDeg/3.0 > DEC_TICK_STEPS[i]['deg']: 901 matchIndex = i 902 # So don't wrap around the unmarked ticks 903 if matchIndex<2: 904 matchIndex=2 905 906 return {'major': DEC_TICK_STEPS[matchIndex], 'minor': DEC_TICK_STEPS[matchIndex-1], 'unlabelled': DEC_TICK_STEPS[matchIndex-2]} 907 908 else: 909 raise Exception, "axis must be 'ra' or 'dec'" 910 911 elif mode == "decimal": 912 913 matchIndex = 0 914 for i in range(len(DECIMAL_TICK_STEPS)): 915 if plotSizeDeg/3.0 > DECIMAL_TICK_STEPS[i]: 916 matchIndex = i 917 # So don't wrap around the unmarked ticks 918 if matchIndex<2: 919 matchIndex=2 920 921 return {'major': DECIMAL_TICK_STEPS[matchIndex], 'minor': DECIMAL_TICK_STEPS[matchIndex-1], 'unlabelled': DECIMAL_TICK_STEPS[matchIndex-2]} 922 923 else: 924 raise Exception, "mode must be either 'sexagesimal' or 'decimal'"
925 926 #--------------------------------------------------------------------------------------------------- 927