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', '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', '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  from scipy import interpolate 
  42  import pylab 
  43  import matplotlib.patches as patches 
  44  import sys 
  45   
  46  DEC_TICK_STEPS=[{'deg': 1.0/60.0/60.0,  'unit': "s"},  
  47                  {'deg': 2.0/60.0/60.0,  'unit': "s"}, 
  48                  {'deg': 5.0/60.0/60.0,  'unit': "s"},  
  49                  {'deg': 10.0/60.0/60.0, 'unit': "s"}, 
  50                  {'deg': 30.0/60.0/60.0, 'unit': "s"}, 
  51                  {'deg': 1.0/60.0,       'unit': "m"}, 
  52                  {'deg': 2.0/60.0,       'unit': "m"}, 
  53                  {'deg': 5.0/60.0,       'unit': "m"}, 
  54                  {'deg': 15.0/60.0,      'unit': "m"}, 
  55                  {'deg': 30.0/60.0,      'unit': "m"},  
  56                  {'deg': 1.0,            'unit': "d"}, 
  57                  {'deg': 2.0,            'unit': "d"}, 
  58                  {'deg': 4.0,            'unit': "d"}, 
  59                  {'deg': 5.0,            'unit': "d"}, 
  60                  {'deg': 10.0,           'unit': "d"}, 
  61                  {'deg': 20.0,           'unit': "d"}, 
  62                  {'deg': 30.0,           'unit': "d"}] 
  63   
  64  RA_TICK_STEPS=[ {'deg': (0.5/60.0/60.0/24.0)*360.0,  'unit': "s"}, 
  65                  {'deg': (1.0/60.0/60.0/24.0)*360.0,  'unit': "s"}, 
  66                  {'deg': (2.0/60.0/60.0/24.0)*360.0,  'unit': "s"},  
  67                  {'deg': (4.0/60.0/60.0/24.0)*360.0,  'unit': "s"},  
  68                  {'deg': (5.0/60.0/60.0/24.0)*360.0,  'unit': "s"},  
  69                  {'deg': (10.0/60.0/60.0/24.0)*360.0, 'unit': "s"}, 
  70                  {'deg': (20.0/60.0/60.0/24.0)*360.0, 'unit': "s"}, 
  71                  {'deg': (30.0/60.0/60.0/24.0)*360.0, 'unit': "s"}, 
  72                  {'deg': (1.0/60.0/24.0)*360.0,       'unit': "m"}, 
  73                  {'deg': (2.0/60.0/24.0)*360.0,       'unit': "m"}, 
  74                  {'deg': (5.0/60.0/24.0)*360.0,       'unit': "m"}, 
  75                  {'deg': (10.0/60.0/24.0)*360.0,      'unit': "m"}, 
  76                  {'deg': (20.0/60.0/24.0)*360.0,      'unit': "m"}, 
  77                  {'deg': (30.0/60.0/24.0)*360.0,      'unit': "m"},  
  78                  {'deg': (1.0/24.0)*360.0,            'unit': "h"}, 
  79                  {'deg': (3.0/24.0)*360.0,            'unit': "h"}, 
  80                  {'deg': (6.0/24.0)*360.0,            'unit': "h"}, 
  81                  {'deg': (12.0/24.0)*360.0,           'unit': "h"}] 
  82   
  83  DECIMAL_TICK_STEPS=[0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.0, 2.5, 5.0, 10.0, 30.0, 90.0] 
  84   
  85  DEG = u"\N{DEGREE SIGN}" 
  86  PRIME = "$^\prime$" 
  87  DOUBLE_PRIME = "$^{\prime\prime}$" 
  88   
  89  #--------------------------------------------------------------------------------------------------- 
90 -class ImagePlot:
91 """This class describes a matplotlib image plot containing an astronomical image with an 92 associated WCS. 93 94 Objects within the image boundaries can be marked by passing their WCS coordinates to 95 L{ImagePlot.addPlotObjects}. 96 97 Other images can be overlaid using L{ImagePlot.addContourOverlay}. 98 99 For images rotated with North at the top, East at the left (as can be done using 100 L{astImages.clipRotatedImageSectionWCS} or L{astImages.resampleToTanProjection}, WCS coordinate 101 axes can be plotted, with tick marks set appropriately for the image size. Otherwise, a compass 102 can be plotted showing the directions of North and East in the image. 103 104 RGB images are also supported. 105 106 The plot can of course be tweaked further after creation using matplotlib/pylab commands. 107 108 """
109 - def __init__(self, imageData, imageWCS, axes = [0.1,0.1,0.8,0.8], \ 110 cutLevels = ["smart", 99.5], colorMapName = "gray", title = None, axesLabels = "sexagesimal", \ 111 axesFontFamily="serif", axesFontSize=12.0, RATickSteps="auto", decTickSteps="auto", 112 colorBar = False):
113 """Makes an ImagePlot from the given image array and astWCS. For coordinate axes to work, the 114 image and WCS should have been rotated such that East is at the left, North is at the top 115 (see e.g. L{astImages.clipRotatedImageSectionWCS}, or L{astImages.resampleToTanProjection}). 116 117 If imageData is given as a list in the format [r, g, b], a color RGB plot will be made. However, 118 in this case the cutLevels must be specified manually for each component as a list - 119 i.e. cutLevels = [[r min, r max], [g min, g max], [b min, b max]]. In this case of course, the 120 colorMap will be ignored. All r, g, b image arrays must have the same dimensions. 121 122 Set axesLabels = None to make a plot without coordinate axes plotted. 123 124 The axes can be marked in either sexagesimal or decimal celestial coordinates. If RATickSteps 125 or decTickSteps are set to "auto", the appropriate axis scales will be determined automatically 126 from the size of the image array and associated WCS. The tick step sizes can be overidden. 127 If the coordinate axes are in sexagesimal format a dictionary in the format {'deg', 'unit'} is 128 needed (see L{RA_TICK_STEPS} and L{DEC_TICK_STEPS} for examples). If the coordinate axes are in 129 decimal format, the tick step size is specified simply in RA, dec decimal degrees. 130 131 @type imageData: numpy array or list 132 @param imageData: image data array or list of numpy arrays [r, g, b] 133 @type imageWCS: astWCS.WCS 134 @param imageWCS: astWCS.WCS object 135 @type axes: list 136 @param axes: specifies where in the current figure to draw the finder chart (see pylab.axes) 137 @type cutLevels: list 138 @param cutLevels: sets the image scaling - available options: 139 - pixel values: cutLevels=[low value, high value]. 140 - histogram equalisation: cutLevels=["histEq", number of bins ( e.g. 1024)] 141 - relative: cutLevels=["relative", cut per cent level (e.g. 99.5)] 142 - smart: cutLevels=["smart", cut per cent level (e.g. 99.5)] 143 ["smart", 99.5] seems to provide good scaling over a range of different images. 144 Note that for RGB images, cut levels must be specified manually i.e. as a list: 145 [[r min, rmax], [g min, g max], [b min, b max]] 146 @type colorMapName: string 147 @param colorMapName: name of a standard matplotlib colormap, e.g. "hot", "cool", "gray" 148 etc. (do "help(pylab.colormaps)" in the Python interpreter to see available options) 149 @type title: string 150 @param title: optional title for the plot 151 @type axesLabels: string 152 @param axesLabels: either "sexagesimal" (for H:M:S, D:M:S), "decimal" (for decimal degrees) 153 or None (for no coordinate axes labels) 154 @type axesFontFamily: string 155 @param axesFontFamily: matplotlib fontfamily, e.g. 'serif', 'sans-serif' etc. 156 @type axesFontSize: float 157 @param axesFontSize: font size of axes labels and titles (in points) 158 @type colorBar: bool 159 @param colorBar: if True, plot a vertical color bar at the side of the image indicating the intensity 160 scale. 161 162 """ 163 164 self.RADeg, self.decDeg=imageWCS.getCentreWCSCoords() 165 self.wcs=imageWCS 166 167 # Handle case where imageData is [r, g, b] 168 if type(imageData) == list: 169 if len(imageData) == 3: 170 if len(cutLevels) == 3: 171 r=astImages.normalise(imageData[0], cutLevels[0]) 172 g=astImages.normalise(imageData[1], cutLevels[1]) 173 b=astImages.normalise(imageData[2], cutLevels[2]) 174 rgb=numpy.array([r.transpose(), g.transpose(), b.transpose()]) 175 rgb=rgb.transpose() 176 self.data=rgb 177 self.rgbImage=True 178 else: 179 raise Exception, "tried to create a RGB array, but cutLevels is not a list of 3 lists" 180 181 else: 182 raise Exception, "tried to create a RGB array but imageData is not a list of 3 arrays" 183 else: 184 self.data=imageData 185 self.rgbImage=False 186 187 self.axes=pylab.axes(axes) 188 self.cutLevels=cutLevels 189 self.colorMapName=colorMapName 190 self.title=title 191 self.axesLabels=axesLabels 192 self.colorBar=colorBar 193 self.axesFontSize=axesFontSize 194 self.axesFontFamily=axesFontFamily 195 196 self.flipXAxis=False 197 self.flipYAxis=False 198 199 if self.axesLabels != None: 200 201 # Allow user to override the automatic coord tick spacing 202 if self.axesLabels == "sexagesimal": 203 if RATickSteps != "auto": 204 if type(RATickSteps) != dict or "deg" not in RATickSteps.keys() \ 205 or "unit" not in RATickSteps.keys(): 206 raise Exception, "RATickSteps needs to be in format {'deg', 'unit'} for sexagesimal axes labels" 207 if decTickSteps != "auto": 208 if type(decTickSteps) != dict or "deg" not in decTickSteps.keys() \ 209 or "unit" not in decTickSteps.keys(): 210 raise Exception, "decTickSteps needs to be in format {'deg', 'unit'} for sexagesimal axes labels" 211 elif self.axesLabels == "decimal": 212 if RATickSteps != "auto": 213 if type(RATickSteps) != float: 214 raise Exception, "RATickSteps needs to be a float (if not 'auto') for decimal axes labels" 215 if decTickSteps != "auto": 216 if type(decTickSteps) != float: 217 raise Exception, "decTickSteps needs to be a float (if not 'auto') for decimal axes labels" 218 self.RATickSteps=RATickSteps 219 self.decTickSteps=decTickSteps 220 221 self.calcWCSAxisLabels(axesLabels = self.axesLabels) 222 223 # this list stores objects to overplot, add to it using addPlotObjects() 224 self.plotObjects=[] 225 226 # this list stores image data to overlay as contours, add to it using addContourOverlay() 227 self.contourOverlays=[] 228 229 self.draw()
230 231
232 - def draw(self):
233 """Redraws the ImagePlot. 234 235 """ 236 237 pylab.axes(self.axes) 238 pylab.cla() 239 240 if self.title != None: 241 pylab.title(self.title) 242 try: 243 colorMap=pylab.cm.get_cmap(self.colorMapName) 244 except AssertionError: 245 raise Exception, self.colorMapName+"is not a defined matplotlib colormap." 246 247 if self.rgbImage == False: 248 self.cutImage=astImages.intensityCutImage(self.data, self.cutLevels) 249 if self.cutLevels[0]=="histEq": 250 pylab.imshow(self.cutImage['image'], interpolation="bilinear", origin='lower', cmap=colorMap) 251 else: 252 pylab.imshow(self.cutImage['image'], interpolation="bilinear", norm=self.cutImage['norm'], \ 253 origin='lower', cmap=colorMap) 254 else: 255 pylab.imshow(self.data, interpolation="bilinear", origin='lower') 256 257 if self.colorBar == True: 258 pylab.colorbar(shrink=0.8) 259 260 for c in self.contourOverlays: 261 pylab.contour(c['contourData']['scaledImage'], c['contourData']['contourLevels'], 262 colors=c['color'], linewidths=c['width']) 263 264 for p in self.plotObjects: 265 for x, y, l in zip(p['x'], p['y'], p['objLabels']): 266 if p['symbol'] == "circle": 267 c=patches.Circle((x, y), radius=p['sizePix']/2.0, fill=False, edgecolor=p['color'], 268 linewidth=p['width']) 269 self.axes.add_patch(c) 270 elif p['symbol'] == "box": 271 c=patches.Rectangle((x-p['sizePix']/2, y-p['sizePix']/2), p['sizePix'], p['sizePix'], 272 fill=False, edgecolor=p['color'], linewidth=p['width']) 273 self.axes.add_patch(c) 274 elif p['symbol'] == "cross": 275 pylab.plot([x-p['sizePix']/2, x+p['sizePix']/2], [y, y], linestyle='-', 276 linewidth=p['width'], color= p['color']) 277 pylab.plot([x, x], [y-p['sizePix']/2, y+p['sizePix']/2], linestyle='-', 278 linewidth=p['width'], color= p['color']) 279 elif p['symbol'] == "diamond": 280 c=patches.RegularPolygon([x, y], 4, radius=p['sizePix']/2, orientation=0, 281 edgecolor=p['color'], fill=False, linewidth=p['width']) 282 self.axes.add_patch(c) 283 if l != None: 284 pylab.text(x, y+p['sizePix']/1.5, l, horizontalalignment='center', \ 285 fontsize=p['objLabelSize'], color=p['color']) 286 287 if p['symbol'] == "compass": 288 x=p['x'][0] 289 y=p['y'][0] 290 ra=p['RA'][0] 291 dec=p['dec'][0] 292 northPoint=dec+p['sizeArcSec']/3600.0 293 eastPoint=ra+p['sizeArcSec']/3600.0 294 sizePix=(p['sizeArcSec']/3600.0)/self.wcs.getPixelSizeDeg() 295 northPix=self.wcs.wcs2pix(ra, northPoint) 296 eastPix=self.wcs.wcs2pix(eastPoint, dec) 297 edx=eastPix[0]-x 298 edy=eastPix[1]-y 299 ndx=northPix[0]-x 300 ndy=northPix[1]-y 301 nArrow=patches.Arrow(x, y, ndx, ndy, edgecolor=p['color'], facecolor=p['color'], width=p['width']) 302 eArrow=patches.Arrow(x, y, edx, edy, edgecolor=p['color'], facecolor=p['color'], width=p['width']) 303 self.axes.add_patch(nArrow) 304 self.axes.add_patch(eArrow) 305 pylab.text(x+ndx+ndx*0.2, y+ndy+ndy*0.2, "N", horizontalalignment='center', 306 verticalalignment='center', fontsize=p['objLabelSize'], color=p['color']) 307 pylab.text(x+edx+edx*0.2, y+edy+edy*0.2, "E", horizontalalignment='center', 308 verticalalignment='center', fontsize=p['objLabelSize'], color=p['color']) 309 310 if p['symbol'] == "scaleBar": 311 x=p['x'][0] 312 y=p['y'][0] 313 ra=p['RA'][0] 314 dec=p['dec'][0] 315 northPoint=dec+p['sizeArcSec']/3600.0 316 eastPoint=ra+p['sizeArcSec']/3600.0 317 sizePix=(p['sizeArcSec']/3600.0)/self.wcs.getPixelSizeDeg() 318 northPix=self.wcs.wcs2pix(ra, northPoint) 319 eastPix=self.wcs.wcs2pix(eastPoint, dec) 320 edx=eastPix[0]-x 321 edy=eastPix[1]-y 322 ndx=northPix[0]-x 323 ndy=northPix[1]-y 324 #nArrow=patches.Arrow(x, y, ndx, ndy, edgecolor=p['color'], facecolor=p['color'], width=p['width']) 325 eArrow=patches.Arrow(x+edx/2, y, edx/2, edy, edgecolor=p['color'], facecolor=p['color'], width=p['width']) 326 wArrow=patches.Arrow(x+edx/2, y, -edx/2, edy, edgecolor=p['color'], facecolor=p['color'], width=p['width']) 327 328 #self.axes.add_patch(nArrow) 329 self.axes.add_patch(eArrow) 330 self.axes.add_patch(wArrow) 331 332 # Work out label 333 scaleLabel=None 334 if p['sizeArcSec'] < 60.0: 335 scaleLabel="%.0f %s" % (p['sizeArcSec'], DOUBLE_PRIME) 336 elif p['sizeArcSec'] >= 60.0 and p['sizeArcSec'] < 3600.0: 337 scaleLabel="%.0f %s" % (p['sizeArcSec']/60.0, PRIME) 338 else: 339 scaleLabel="%.0f %s" % (p['sizeArcSec']/3600.0, DEG) 340 341 pylab.text(x+edx/2, y+0.025*self.data.shape[1], scaleLabel, horizontalalignment='center', 342 verticalalignment='center', fontsize=p['objLabelSize'], color=p['color']) 343 #pylab.text(x+edx+edx*0.2, y+edy+edy*0.2, "E", horizontalalignment='center', 344 #verticalalignment='center', fontsize=p['objLabelSize'], color=p['color']) 345 346 if self.axesLabels != None: 347 pylab.xticks(self.ticsRA[0], self.ticsRA[1], weight='normal', family=self.axesFontFamily, \ 348 fontsize=self.axesFontSize) 349 pylab.yticks(self.ticsDec[0], self.ticsDec[1], weight='normal', family=self.axesFontFamily, \ 350 fontsize=self.axesFontSize) 351 pylab.xlabel(self.RAAxisLabel, family=self.axesFontFamily, fontsize=self.axesFontSize) 352 pylab.ylabel(self.decAxisLabel, family=self.axesFontFamily, fontsize=self.axesFontSize) 353 else: 354 pylab.xticks([], []) 355 pylab.yticks([], []) 356 pylab.xlabel("") 357 pylab.ylabel("") 358 359 if self.flipXAxis == False: 360 pylab.xlim(0, self.data.shape[1]-1) 361 else: 362 pylab.xlim(self.data.shape[1]-1, 0) 363 if self.flipYAxis == False: 364 pylab.ylim(0, self.data.shape[0]-1) 365 else: 366 pylab.ylim(self.data.shape[0]-1, 0)
367 368
369 - def addContourOverlay(self, contourImageData, contourWCS, tag, levels = ["linear", "min", "max", 5], 370 width = 1, color = "white", smooth = 0, highAccuracy = False):
371 """Adds image data to the ImagePlot as a contour overlay. The contours can be removed using L{removeContourOverlay}. If a contour overlay already exists with this tag, it will be replaced. 372 373 @type contourImageData: numpy array 374 @param contourImageData: image data array from which contours are to be generated 375 @type contourWCS: astWCS.WCS 376 @param contourWCS: astWCS.WCS object for the image to be contoured 377 @type tag: string 378 @param tag: identifying tag for this set of contours 379 @type levels: list 380 @param levels: sets the contour levels - available options: 381 - values: contourLevels=[list of values specifying each level] 382 - linear spacing: contourLevels=['linear', min level value, max level value, number 383 of levels] - can use "min", "max" to automatically set min, max levels from image data 384 - log spacing: contourLevels=['log', min level value, max level value, number of 385 levels] - can use "min", "max" to automatically set min, max levels from image data 386 @type width: int 387 @param width: width of the overlaid contours 388 @type color: string 389 @param color: color of the overlaid contours, specified by the name of a standard 390 matplotlib color, e.g., "black", "white", "cyan" 391 etc. (do "help(pylab.colors)" in the Python interpreter to see available options) 392 @type smooth: float 393 @param smooth: standard deviation (in arcsec) of Gaussian filter for 394 pre-smoothing of contour image data (set to 0 for no smoothing) 395 @type highAccuracy: bool 396 @param highAccuracy: if True, sample every corresponding pixel in each image; otherwise, sample 397 every nth pixel, where n = the ratio of the image scales. 398 399 """ 400 401 if self.rgbImage == True: 402 backgroundData=self.data[:,:,0] 403 else: 404 backgroundData=self.data 405 contourData=astImages.generateContourOverlay(backgroundData, self.wcs, contourImageData, \ 406 contourWCS, levels, smooth, highAccuracy = highAccuracy) 407 408 alreadyGot=False 409 for c in self.contourOverlays: 410 if c['tag'] == tag: 411 c['contourData']=contourData 412 c['tag']=tag 413 c['color']=color 414 c['width']=width 415 alreadyGot=True 416 417 if alreadyGot == False: 418 self.contourOverlays.append({'contourData': contourData, 'tag': tag, 'color': color, \ 419 'width': width}) 420 self.draw()
421 422
423 - def removeContourOverlay(self, tag):
424 """Removes the contourOverlay from the ImagePlot corresponding to the tag. 425 426 @type tag: string 427 @param tag: tag for contour overlay in ImagePlot.contourOverlays to be removed 428 429 """ 430 431 index=0 432 for p in self.contourOverlays: 433 if p['tag'] == tag: 434 self.plotObjects.remove(self.plotObjects[index]) 435 index=index+1 436 self.draw()
437 438
439 - def addPlotObjects(self, objRAs, objDecs, tag, symbol="circle", size=4.0, width=1.0, color="yellow", 440 objLabels = None, objLabelSize = 12.0):
441 """Add objects with RA, dec coords objRAs, objDecs to the ImagePlot. Only objects that fall within 442 the image boundaries will be plotted. 443 444 symbol specifies the type of symbol with which to mark the object in the image. The following 445 values are allowed: 446 - "circle" 447 - "box" 448 - "cross" 449 - "diamond" 450 451 size specifies the diameter in arcsec of the symbol (if plotSymbol == "circle"), or the width 452 of the box in arcsec (if plotSymbol == "box") 453 454 width specifies the thickness of the symbol lines in pixels 455 456 color can be any valid matplotlib color (e.g. "red", "green", etc.) 457 458 The objects can be removed from the plot by using removePlotObjects(), and then calling 459 draw(). If the ImagePlot already has a set of plotObjects with the same tag, they will be 460 replaced. 461 462 @type objRAs: numpy array or list 463 @param objRAs: object RA coords in decimal degrees 464 @type objDecs: numpy array or list 465 @param objDecs: corresponding object Dec. coords in decimal degrees 466 @type tag: string 467 @param tag: identifying tag for this set of objects 468 @type symbol: string 469 @param symbol: either "circle", "box", "cross", or "diamond" 470 @type size: float 471 @param size: size of symbols to plot (radius in arcsec, or width of box) 472 @type width: float 473 @param width: width of symbols in pixels 474 @type color: string 475 @param color: any valid matplotlib color string, e.g. "red", "green" etc. 476 @type objLabels: list 477 @param objLabels: text labels to plot next to objects in figure 478 @type objLabelSize: float 479 @param objLabelSize: size of font used for object labels (in points) 480 481 """ 482 483 pixCoords=self.wcs.wcs2pix(objRAs, objDecs) 484 485 xMax=self.data.shape[1] 486 yMax=self.data.shape[0] 487 488 if objLabels == None: 489 objLabels=[None]*len(objRAs) 490 491 xInPlot=[] 492 yInPlot=[] 493 RAInPlot=[] 494 decInPlot=[] 495 labelInPlot=[] 496 for p, r, d, l in zip(pixCoords, objRAs, objDecs, objLabels): 497 if p[0] >= 0 and p[0] < xMax and p[1] >= 0 and p[1] < yMax: 498 xInPlot.append(p[0]) 499 yInPlot.append(p[1]) 500 RAInPlot.append(r) 501 decInPlot.append(d) 502 labelInPlot.append(l) 503 504 xInPlot=numpy.array(xInPlot) 505 yInPlot=numpy.array(yInPlot) 506 RAInPlot=numpy.array(RAInPlot) 507 decInPlot=numpy.array(decInPlot) 508 509 # Size of symbols in pixels in plot - converted from arcsec 510 sizePix=(size/3600.0)/self.wcs.getPixelSizeDeg() 511 512 alreadyGot=False 513 for p in self.plotObjects: 514 if p['tag'] == tag: 515 p['x']=xInPlot 516 p['y']=yInPlot 517 p['RA']=RAInPlot 518 p['dec']=decInPlot 519 p['tag']=tag 520 p['objLabels']=objLabels 521 p['symbol']=symbol 522 p['sizePix']=sizePix 523 p['sizeArcSec']=size 524 p['width']=width 525 p['color']=color 526 p['objLabelSize']=objLabelSize 527 alreadyGot=True 528 529 if alreadyGot == False: 530 self.plotObjects.append({'x': xInPlot, 'y': yInPlot, 'RA': RAInPlot, 'dec': decInPlot, 531 'tag': tag, 'objLabels': labelInPlot, 'symbol': symbol, 532 'sizePix': sizePix, 'width': width, 'color': color, 533 'objLabelSize': objLabelSize, 'sizeArcSec': size}) 534 self.draw()
535 536
537 - def removePlotObjects(self, tag):
538 """Removes the plotObjects from the ImagePlot corresponding to the tag. The plot must be redrawn 539 for the change to take effect. 540 541 @type tag: string 542 @param tag: tag for set of objects in ImagePlot.plotObjects to be removed 543 544 """ 545 546 index=0 547 for p in self.plotObjects: 548 if p['tag'] == tag: 549 self.plotObjects.remove(self.plotObjects[index]) 550 index=index+1 551 self.draw()
552 553
554 - def addCompass(self, location, sizeArcSec, color = "white", fontSize = 12, \ 555 width = 20.0):
556 """Adds a compass to the ImagePlot at the given location ('N', 'NE', 'E', 'SE', 'S', 557 'SW', 'W', or 'NW'). Note these aren't directions on the WCS coordinate grid, they are 558 relative positions on the plot - so N is top centre, NE is top right, SW is bottom right etc.. 559 Alternatively, pixel coordinates (x, y) in the image can be given. 560 561 @type location: string or tuple 562 @param location: location in the plot where the compass is drawn: 563 - string: N, NE, E, SE, S, SW, W or NW 564 - tuple: (x, y) 565 @type sizeArcSec: float 566 @param sizeArcSec: length of the compass arrows on the plot in arc seconds 567 @type color: string 568 @param color: any valid matplotlib color string 569 @type fontSize: float 570 @param fontSize: size of font used to label N and E, in points 571 @type width: float 572 @param width: width of arrows used to mark compass 573 574 """ 575 576 # Work out where the compass is going in WCS coords from the relative location given 577 # Draw the compass 1.5 * the given size in arcsec from that edge of the plot 578 if type(location) == str: 579 RADeg, decDeg=self.wcs.getCentreWCSCoords() 580 x, y=self.wcs.wcs2pix(RADeg, decDeg) 581 halfWidthRADeg, halfHeightDecDeg=self.wcs.getHalfSizeDeg() 582 compassDistRADeg=halfWidthRADeg-1.5*sizeArcSec/3600.0 583 compassDistDecDeg=halfHeightDecDeg-1.5*sizeArcSec/3600.0 584 if self.wcs.isFlipped() == True: 585 compassDistRADeg=compassDistRADeg*-1 586 foundLocation=False 587 if location.find("N") != -1: 588 y=y+compassDistDecDeg/self.wcs.getPixelSizeDeg() 589 foundLocation=True 590 if location.find("S") != -1: 591 y=y-compassDistDecDeg/self.wcs.getPixelSizeDeg() 592 foundLocation=True 593 if location.find("E") != -1: 594 x=x-compassDistRADeg/self.wcs.getPixelSizeDeg() 595 foundLocation=True 596 if location.find("W") != -1: 597 x=x+compassDistRADeg/self.wcs.getPixelSizeDeg() 598 foundLocation=True 599 if foundLocation == False: 600 raise Exception, "didn't understand location string for compass (should be e.g. N, S, E, W)." 601 elif type(location) == tuple: 602 x, y=location 603 else: 604 raise Exception, "didn't understand location for compass - should be string or tuple." 605 RADeg, decDeg=self.wcs.pix2wcs(x, y) 606 607 # Size of compass arrows pixels in plot - converted from arcsec 608 sizePix=(sizeArcSec/3600.0)/self.wcs.getPixelSizeDeg() 609 610 alreadyGot=False 611 for p in self.plotObjects: 612 if p['tag'] == "compass": 613 p['x']=[x] 614 p['y']=[y] 615 p['RA']=[RADeg] 616 p['dec']=[decDeg] 617 p['tag']="compass" 618 p['objLabels']=[None] 619 p['symbol']="compass" 620 p['sizePix']=sizePix 621 p['sizeArcSec']=sizeArcSec 622 p['width']=width 623 p['color']=color 624 p['objLabelSize']=fontSize 625 alreadyGot=True 626 627 if alreadyGot == False: 628 self.plotObjects.append({'x': [x], 'y': [y], 'RA': [RADeg], 'dec': [decDeg], 629 'tag': "compass", 'objLabels': [None], 'symbol': "compass", 630 'sizePix': sizePix, 'width': width, 'color': color, 631 'objLabelSize': fontSize, 'sizeArcSec': sizeArcSec}) 632 self.draw()
633 634
635 - def addScaleBar(self, location, sizeArcSec, color = "white", fontSize = 12, \ 636 width = 20.0):
637 """Adds a scale bar to the ImagePlot at the given location ('N', 'NE', 'E', 'SE', 'S', 638 'SW', 'W', or 'NW'). Note these aren't directions on the WCS coordinate grid, they are 639 relative positions on the plot - so N is top centre, NE is top right, SW is bottom right etc.. 640 Alternatively, pixel coordinates (x, y) in the image can be given. 641 642 @type location: string or tuple 643 @param location: location in the plot where the compass is drawn: 644 - string: N, NE, E, SE, S, SW, W or NW 645 - tuple: (x, y) 646 @type sizeArcSec: float 647 @param sizeArcSec: scale length to indicate on the plot in arc seconds 648 @type color: string 649 @param color: any valid matplotlib color string 650 @type fontSize: float 651 @param fontSize: size of font used to label N and E, in points 652 @type width: float 653 @param width: width of arrow used to mark scale 654 655 """ 656 657 # Work out where the scale abr is going in WCS coords from the relative location given 658 if type(location) == str: 659 RADeg, decDeg=self.wcs.getCentreWCSCoords() 660 x, y=self.wcs.wcs2pix(RADeg, decDeg) 661 halfWidthRADeg, halfHeightDecDeg=self.wcs.getHalfSizeDeg() 662 compassDistRADeg=halfWidthRADeg-1.2*sizeArcSec/3600.0 663 compassDistDecDeg=halfHeightDecDeg-0.15*halfHeightDecDeg 664 if self.wcs.isFlipped() == True: 665 compassDistRADeg=compassDistRADeg*-1 666 foundLocation=False 667 if location.find("N") != -1: 668 y=y+compassDistDecDeg/self.wcs.getPixelSizeDeg() 669 foundLocation=True 670 if location.find("S") != -1: 671 y=y-compassDistDecDeg/self.wcs.getPixelSizeDeg() 672 foundLocation=True 673 if location.find("E") != -1: 674 x=x-compassDistRADeg/self.wcs.getPixelSizeDeg() 675 foundLocation=True 676 if location.find("W") != -1: 677 x=x+compassDistRADeg/self.wcs.getPixelSizeDeg() 678 foundLocation=True 679 if foundLocation == False: 680 raise Exception, "didn't understand location string for compass (should be e.g. N, S, E, W)." 681 elif type(location) == tuple: 682 x, y=location 683 else: 684 raise Exception, "didn't understand location for compass - should be string or tuple." 685 RADeg, decDeg=self.wcs.pix2wcs(x, y) 686 687 # Size of scale arrow in pixels on plot - converted from arcsec 688 sizePix=(sizeArcSec/3600.0)/self.wcs.getPixelSizeDeg() 689 690 alreadyGot=False 691 for p in self.plotObjects: 692 if p['tag'] == "scaleBar": 693 p['x']=[x] 694 p['y']=[y] 695 p['RA']=[RADeg] 696 p['dec']=[decDeg] 697 p['tag']="scaleBar" 698 p['objLabels']=[None] 699 p['symbol']="scaleBar" 700 p['sizePix']=sizePix 701 p['sizeArcSec']=sizeArcSec 702 p['width']=width 703 p['color']=color 704 p['objLabelSize']=fontSize 705 alreadyGot=True 706 707 if alreadyGot == False: 708 self.plotObjects.append({'x': [x], 'y': [y], 'RA': [RADeg], 'dec': [decDeg], 709 'tag': "scaleBar", 'objLabels': [None], 'symbol': "scaleBar", 710 'sizePix': sizePix, 'width': width, 'color': color, 711 'objLabelSize': fontSize, 'sizeArcSec': sizeArcSec}) 712 self.draw()
713 714
715 - def calcWCSAxisLabels(self, axesLabels = "decimal"):
716 """ 717 This function calculates the positions of coordinate labels for the RA and Dec axes of the 718 ImagePlot. The tick steps are calculated automatically unless self.RATickSteps, 719 self.decTickSteps are set to values other than "auto" (see L{ImagePlot.__init__}). 720 721 The ImagePlot must be redrawn for changes to be applied. 722 723 @type axesLabels: string 724 @param axesLabels: either "sexagesimal" (for H:M:S, D:M:S), "decimal" (for decimal degrees), 725 or None for no coordinate axes labels 726 727 """ 728 729 # Label equinox on axes 730 equinox=self.wcs.getEquinox() 731 if equinox<1984: 732 equinoxLabel="B"+str(int(equinox)) 733 else: 734 equinoxLabel="J"+str(int(equinox)) 735 736 self.axesLabels=axesLabels 737 738 ticsDict=self.getTickSteps() 739 740 # Manual override - note: no minor tick marks anymore, but may want to bring them back 741 if self.RATickSteps != "auto": 742 ticsDict['major']['RA']=self.RATickSteps 743 if self.decTickSteps != "auto": 744 ticsDict['major']['dec']=self.decTickSteps 745 746 RALocs=[] 747 decLocs=[] 748 RALabels=[] 749 decLabels=[] 750 key="major" 751 #for key in ticsDict.keys(): # key is major or minor 752 if self.axesLabels == "sexagesimal": 753 self.RAAxisLabel="R.A. ("+equinoxLabel+")" 754 self.decAxisLabel="Dec. ("+equinoxLabel+")" 755 RADegStep=ticsDict[key]['RA']['deg'] 756 decDegStep=ticsDict[key]['dec']['deg'] 757 elif self.axesLabels == "decimal": 758 self.RAAxisLabel="R.A. Degrees ("+equinoxLabel+")" 759 self.decAxisLabel="Dec. Degrees ("+equinoxLabel+")" 760 RADegStep=ticsDict[key]['RA'] 761 decDegStep=ticsDict[key]['dec'] 762 else: 763 raise Exception, "axesLabels must be either 'sexagesimal' or 'decimal'" 764 765 xArray=numpy.arange(0, self.data.shape[1], 1) 766 yArray=numpy.arange(0, self.data.shape[0], 1) 767 xWCS=self.wcs.pix2wcs(xArray, [0]*xArray.shape[0]) 768 yWCS=self.wcs.pix2wcs([0]*yArray.shape[0], yArray) 769 xWCS=numpy.array(xWCS) 770 yWCS=numpy.array(yWCS) 771 ras=xWCS[:,0] 772 decs=yWCS[:,1] 773 RAEdges=numpy.array([ras[0], ras[-1]]) 774 RAMin=RAEdges.min() 775 RAMax=RAEdges.max() 776 decMin=decs.min() 777 decMax=decs.max() 778 779 # Work out if wrapped around 780 midRAPix, midDecPix=self.wcs.wcs2pix((RAEdges[1]+RAEdges[0])/2.0, (decMax+decMin)/2.0) 781 if midRAPix < 0 or midRAPix > self.wcs.header['NAXIS1']: 782 wrappedRA=True 783 else: 784 wrappedRA=False 785 786 # Note RA, dec work in opposite sense below because E at left 787 if ras[1] < ras[0]: 788 self.flipXAxis=False 789 ra2x=interpolate.interp1d(ras[::-1], xArray[::-1], kind='linear') 790 else: 791 self.flipXAxis=True 792 ra2x=interpolate.interp1d(ras, xArray, kind='linear') 793 if decs[1] < decs[0]: 794 self.flipYAxis=True 795 dec2y=interpolate.interp1d(decs[::-1], yArray[::-1], kind='linear') 796 else: 797 self.flipYAxis=False 798 dec2y=interpolate.interp1d(decs, yArray, kind='linear') 799 800 if wrappedRA == False: 801 RAPlotMin=RADegStep*math.modf(RAMin/RADegStep)[1] 802 RAPlotMax=RADegStep*math.modf(RAMax/RADegStep)[1] 803 if RAPlotMin < RAMin: 804 RAPlotMin=RAPlotMin+RADegStep 805 if RAPlotMax >= RAMax: 806 RAPlotMax=RAPlotMax-RADegStep 807 RADegs=numpy.arange(RAPlotMin, RAPlotMax+0.0001, RADegStep) 808 else: 809 RAPlotMin=RADegStep*math.modf(RAMin/RADegStep)[1] 810 RAPlotMax=RADegStep*math.modf(RAMax/RADegStep)[1] 811 if RAPlotMin > RAMin: 812 RAPlotMin=RAPlotMin-RADegStep 813 if RAPlotMax <= RAMax: 814 RAPlotMax=RAPlotMax+RADegStep 815 for i in range(ras.shape[0]): 816 if ras[i] >= RAMax and ras[i] <= 360.0: 817 ras[i]=ras[i]-360.0 818 ra2x=interpolate.interp1d(ras[::-1], xArray[::-1], kind='linear') 819 RADegs=numpy.arange(RAPlotMin, RAPlotMax-360.0-0.0001, -RADegStep) 820 821 decPlotMin=decDegStep*math.modf(decMin/decDegStep)[1] 822 decPlotMax=decDegStep*math.modf(decMax/decDegStep)[1] 823 if decPlotMin < decMin: 824 decPlotMin=decPlotMin+decDegStep 825 if decPlotMax >= decMax: 826 decPlotMax=decPlotMax-decDegStep 827 decDegs=numpy.arange(decPlotMin, decPlotMax+0.0001, decDegStep) 828 829 if key == "major": 830 if axesLabels == "sexagesimal": 831 for r in RADegs: 832 if r < 0: 833 r=r+360.0 834 h, m, s=astCoords.decimal2hms(r, ":").split(":") 835 hInt=int(round(float(h))) 836 if hInt < 10: 837 hString="0"+str(hInt) 838 else: 839 hString=str(hInt) 840 mInt=int(round(float(m))) 841 if mInt < 10: 842 mString="0"+str(mInt) 843 else: 844 mString=str(mInt) 845 sInt=int(round(float(s))) 846 if sInt < 10: 847 sString="0"+str(sInt) 848 else: 849 sString=str(sInt) 850 if ticsDict[key]['RA']['unit'] == 'h': 851 rString=hString+"$^h$" 852 elif ticsDict[key]['RA']['unit'] == 'm': 853 rString=hString+"$^h$"+mString+"$^m$" 854 else: 855 rString=hString+"$^h$"+mString+"$^m$"+sString+"$^s$" 856 RALabels.append(rString) 857 for D in decDegs: 858 d, m, s=astCoords.decimal2dms(D, ":").split(":") 859 dInt=int(round(float(d))) 860 if dInt < 10 and dInt >= 1 : 861 dString="+0"+str(dInt) 862 elif dInt > -10 and dInt <=-1: 863 dString="-0"+str(abs(dInt)) 864 elif dInt >= 10: 865 dString="+"+str(dInt) 866 else: 867 dString=str(dInt) 868 mInt=int(round(float(m))) 869 if mInt < 10: 870 mString="0"+str(mInt) 871 else: 872 mString=str(mInt) 873 sInt=int(round(float(s))) 874 if sInt < 10: 875 sString="0"+str(sInt) 876 else: 877 sString=str(sInt) 878 if ticsDict[key]['dec']['unit'] == 'd': 879 dString=dString+DEG 880 elif ticsDict[key]['dec']['unit'] == 'm': 881 dString=dString+DEG+mString+PRIME 882 else: 883 dString=dString+DEG+mString+PRIME+sString+DOUBLE_PRIME 884 decLabels.append(dString) 885 elif axesLabels == "decimal": 886 if wrappedRA == False: 887 RALabels=RALabels+RADegs.tolist() 888 else: 889 nonNegativeLabels=[] 890 for r in RADegs: 891 if r < 0: 892 r=r+360.0 893 nonNegativeLabels.append(r) 894 RALabels=RALabels+nonNegativeLabels 895 decLabels=decLabels+decDegs.tolist() 896 897 # Format RALabels, decLabels to same number of d.p. 898 dps=[] 899 for r in RALabels: 900 dps.append(len(str(r).split(".")[-1])) 901 dpNumRA=int(math.ceil(numpy.array(dps).mean())) 902 for i in range(len(RALabels)): 903 fString="%."+str(dpNumRA)+"f" 904 RALabels[i]=fString % (RALabels[i]) 905 dps=[] 906 for d in decLabels: 907 dps.append(len(str(d).split(".")[-1])) 908 dpNumDec=int(math.ceil(numpy.array(dps).mean())) 909 for i in range(len(decLabels)): 910 fString="%."+str(dpNumDec)+"f" 911 decLabels[i]=fString % (decLabels[i]) 912 913 if key == 'minor': 914 RALabels=RALabels+RADegs.shape[0]*[''] 915 decLabels=decLabels+decDegs.shape[0]*[''] 916 917 RALocs=RALocs+ra2x(RADegs).tolist() 918 decLocs=decLocs+dec2y(decDegs).tolist() 919 920 self.ticsRA=[RALocs, RALabels] 921 self.ticsDec=[decLocs, decLabels]
922 923
924 - def save(self, fileName):
925 """Saves the ImagePlot in any format that matplotlib can understand, as determined from the 926 fileName extension. 927 928 @type fileName: string 929 @param fileName: path where plot will be written 930 931 """ 932 933 pylab.draw() 934 pylab.savefig(fileName)
935 936
937 - def getTickSteps(self):
938 """Chooses the appropriate WCS coordinate tick steps for the plot based on its size. 939 Whether the ticks are decimal or sexagesimal is set by self.axesLabels. 940 941 Note: minor ticks not used at the moment. 942 943 @rtype: dictionary 944 @return: tick step sizes for major, minor plot ticks, in format {'major', 'minor'} 945 946 """ 947 948 # Aim for 5 major tick marks on a plot 949 xArray=numpy.arange(0, self.data.shape[1], 1) 950 yArray=numpy.arange(0, self.data.shape[0], 1) 951 xWCS=self.wcs.pix2wcs(xArray, [0]*xArray.shape[0]) 952 yWCS=self.wcs.pix2wcs([0]*yArray.shape[0], yArray) 953 xWCS=numpy.array(xWCS) 954 yWCS=numpy.array(yWCS) 955 ras=xWCS[:,0] 956 decs=yWCS[:,1] 957 RAEdges=numpy.array([ras[0], ras[-1]]) 958 RAMin=RAEdges.min() 959 RAMax=RAEdges.max() 960 decMin=decs.min() 961 decMax=decs.max() 962 963 # Work out if wrapped around 964 midRAPix, midDecPix=self.wcs.wcs2pix((RAEdges[1]+RAEdges[0])/2.0, (decMax+decMin)/2.0) 965 if midRAPix < 0 or midRAPix > self.wcs.header['NAXIS1']: 966 wrappedRA=True 967 else: 968 wrappedRA=False 969 if wrappedRA == False: 970 RAWidthDeg=RAMax-RAMin 971 else: 972 RAWidthDeg=(360.0-RAMax)+RAMin 973 decHeightDeg=decMax-decMin 974 975 ticsDict={} 976 ticsDict['major']={} 977 ticsDict['minor']={} 978 if self.axesLabels == "sexagesimal": 979 980 matchIndex = 0 981 for i in range(len(RA_TICK_STEPS)): 982 if RAWidthDeg/2.5 > RA_TICK_STEPS[i]['deg']: 983 matchIndex = i 984 985 ticsDict['major']['RA']=RA_TICK_STEPS[matchIndex] 986 ticsDict['minor']['RA']=RA_TICK_STEPS[matchIndex-1] 987 988 matchIndex = 0 989 for i in range(len(DEC_TICK_STEPS)): 990 if decHeightDeg/2.5 > DEC_TICK_STEPS[i]['deg']: 991 matchIndex = i 992 993 ticsDict['major']['dec']=DEC_TICK_STEPS[matchIndex] 994 ticsDict['minor']['dec']=DEC_TICK_STEPS[matchIndex-1] 995 996 return ticsDict 997 998 elif self.axesLabels == "decimal": 999 1000 matchIndex = 0 1001 for i in range(len(DECIMAL_TICK_STEPS)): 1002 if RAWidthDeg/2.5 > DECIMAL_TICK_STEPS[i]: 1003 matchIndex = i 1004 1005 ticsDict['major']['RA']=DECIMAL_TICK_STEPS[matchIndex] 1006 ticsDict['minor']['RA']=DECIMAL_TICK_STEPS[matchIndex-1] 1007 1008 matchIndex = 0 1009 for i in range(len(DECIMAL_TICK_STEPS)): 1010 if decHeightDeg/2.5 > DECIMAL_TICK_STEPS[i]: 1011 matchIndex = i 1012 1013 ticsDict['major']['dec']=DECIMAL_TICK_STEPS[matchIndex] 1014 ticsDict['minor']['dec']=DECIMAL_TICK_STEPS[matchIndex-1] 1015 1016 return ticsDict 1017 1018 else: 1019 raise Exception, "axesLabels must be either 'sexagesimal' or 'decimal'"
1020