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-2011 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 372 L{removeContourOverlay}. If a contour overlay already exists with this tag, it will be replaced. 373 374 @type contourImageData: numpy array 375 @param contourImageData: image data array from which contours are to be generated 376 @type contourWCS: astWCS.WCS 377 @param contourWCS: astWCS.WCS object for the image to be contoured 378 @type tag: string 379 @param tag: identifying tag for this set of contours 380 @type levels: list 381 @param levels: sets the contour levels - available options: 382 - values: contourLevels=[list of values specifying each level] 383 - linear spacing: contourLevels=['linear', min level value, max level value, number 384 of levels] - can use "min", "max" to automatically set min, max levels from image data 385 - log spacing: contourLevels=['log', min level value, max level value, number of 386 levels] - can use "min", "max" to automatically set min, max levels from image data 387 @type width: int 388 @param width: width of the overlaid contours 389 @type color: string 390 @param color: color of the overlaid contours, specified by the name of a standard 391 matplotlib color, e.g., "black", "white", "cyan" 392 etc. (do "help(pylab.colors)" in the Python interpreter to see available options) 393 @type smooth: float 394 @param smooth: standard deviation (in arcsec) of Gaussian filter for 395 pre-smoothing of contour image data (set to 0 for no smoothing) 396 @type highAccuracy: bool 397 @param highAccuracy: if True, sample every corresponding pixel in each image; otherwise, sample 398 every nth pixel, where n = the ratio of the image scales. 399 400 """ 401 402 if self.rgbImage == True: 403 backgroundData=self.data[:,:,0] 404 else: 405 backgroundData=self.data 406 contourData=astImages.generateContourOverlay(backgroundData, self.wcs, contourImageData, \ 407 contourWCS, levels, smooth, highAccuracy = highAccuracy) 408 409 alreadyGot=False 410 for c in self.contourOverlays: 411 if c['tag'] == tag: 412 c['contourData']=contourData 413 c['tag']=tag 414 c['color']=color 415 c['width']=width 416 alreadyGot=True 417 418 if alreadyGot == False: 419 self.contourOverlays.append({'contourData': contourData, 'tag': tag, 'color': color, \ 420 'width': width}) 421 self.draw()
422 423
424 - def removeContourOverlay(self, tag):
425 """Removes the contourOverlay from the ImagePlot corresponding to the tag. 426 427 @type tag: string 428 @param tag: tag for contour overlay in ImagePlot.contourOverlays to be removed 429 430 """ 431 432 index=0 433 for p in self.contourOverlays: 434 if p['tag'] == tag: 435 self.plotObjects.remove(self.plotObjects[index]) 436 index=index+1 437 self.draw()
438 439
440 - def addPlotObjects(self, objRAs, objDecs, tag, symbol="circle", size=4.0, width=1.0, color="yellow", 441 objLabels = None, objLabelSize = 12.0):
442 """Add objects with RA, dec coords objRAs, objDecs to the ImagePlot. Only objects that fall within 443 the image boundaries will be plotted. 444 445 symbol specifies the type of symbol with which to mark the object in the image. The following 446 values are allowed: 447 - "circle" 448 - "box" 449 - "cross" 450 - "diamond" 451 452 size specifies the diameter in arcsec of the symbol (if plotSymbol == "circle"), or the width 453 of the box in arcsec (if plotSymbol == "box") 454 455 width specifies the thickness of the symbol lines in pixels 456 457 color can be any valid matplotlib color (e.g. "red", "green", etc.) 458 459 The objects can be removed from the plot by using removePlotObjects(), and then calling 460 draw(). If the ImagePlot already has a set of plotObjects with the same tag, they will be 461 replaced. 462 463 @type objRAs: numpy array or list 464 @param objRAs: object RA coords in decimal degrees 465 @type objDecs: numpy array or list 466 @param objDecs: corresponding object Dec. coords in decimal degrees 467 @type tag: string 468 @param tag: identifying tag for this set of objects 469 @type symbol: string 470 @param symbol: either "circle", "box", "cross", or "diamond" 471 @type size: float 472 @param size: size of symbols to plot (radius in arcsec, or width of box) 473 @type width: float 474 @param width: width of symbols in pixels 475 @type color: string 476 @param color: any valid matplotlib color string, e.g. "red", "green" etc. 477 @type objLabels: list 478 @param objLabels: text labels to plot next to objects in figure 479 @type objLabelSize: float 480 @param objLabelSize: size of font used for object labels (in points) 481 482 """ 483 484 pixCoords=self.wcs.wcs2pix(objRAs, objDecs) 485 486 xMax=self.data.shape[1] 487 yMax=self.data.shape[0] 488 489 if objLabels == None: 490 objLabels=[None]*len(objRAs) 491 492 xInPlot=[] 493 yInPlot=[] 494 RAInPlot=[] 495 decInPlot=[] 496 labelInPlot=[] 497 for p, r, d, l in zip(pixCoords, objRAs, objDecs, objLabels): 498 if p[0] >= 0 and p[0] < xMax and p[1] >= 0 and p[1] < yMax: 499 xInPlot.append(p[0]) 500 yInPlot.append(p[1]) 501 RAInPlot.append(r) 502 decInPlot.append(d) 503 labelInPlot.append(l) 504 505 xInPlot=numpy.array(xInPlot) 506 yInPlot=numpy.array(yInPlot) 507 RAInPlot=numpy.array(RAInPlot) 508 decInPlot=numpy.array(decInPlot) 509 510 # Size of symbols in pixels in plot - converted from arcsec 511 sizePix=(size/3600.0)/self.wcs.getPixelSizeDeg() 512 513 alreadyGot=False 514 for p in self.plotObjects: 515 if p['tag'] == tag: 516 p['x']=xInPlot 517 p['y']=yInPlot 518 p['RA']=RAInPlot 519 p['dec']=decInPlot 520 p['tag']=tag 521 p['objLabels']=objLabels 522 p['symbol']=symbol 523 p['sizePix']=sizePix 524 p['sizeArcSec']=size 525 p['width']=width 526 p['color']=color 527 p['objLabelSize']=objLabelSize 528 alreadyGot=True 529 530 if alreadyGot == False: 531 self.plotObjects.append({'x': xInPlot, 'y': yInPlot, 'RA': RAInPlot, 'dec': decInPlot, 532 'tag': tag, 'objLabels': labelInPlot, 'symbol': symbol, 533 'sizePix': sizePix, 'width': width, 'color': color, 534 'objLabelSize': objLabelSize, 'sizeArcSec': size}) 535 self.draw()
536 537
538 - def removePlotObjects(self, tag):
539 """Removes the plotObjects from the ImagePlot corresponding to the tag. The plot must be redrawn 540 for the change to take effect. 541 542 @type tag: string 543 @param tag: tag for set of objects in ImagePlot.plotObjects to be removed 544 545 """ 546 547 index=0 548 for p in self.plotObjects: 549 if p['tag'] == tag: 550 self.plotObjects.remove(self.plotObjects[index]) 551 index=index+1 552 self.draw()
553 554
555 - def addCompass(self, location, sizeArcSec, color = "white", fontSize = 12, \ 556 width = 20.0):
557 """Adds a compass to the ImagePlot at the given location ('N', 'NE', 'E', 'SE', 'S', 558 'SW', 'W', or 'NW'). Note these aren't directions on the WCS coordinate grid, they are 559 relative positions on the plot - so N is top centre, NE is top right, SW is bottom right etc.. 560 Alternatively, pixel coordinates (x, y) in the image can be given. 561 562 @type location: string or tuple 563 @param location: location in the plot where the compass is drawn: 564 - string: N, NE, E, SE, S, SW, W or NW 565 - tuple: (x, y) 566 @type sizeArcSec: float 567 @param sizeArcSec: length of the compass arrows on the plot in arc seconds 568 @type color: string 569 @param color: any valid matplotlib color string 570 @type fontSize: float 571 @param fontSize: size of font used to label N and E, in points 572 @type width: float 573 @param width: width of arrows used to mark compass 574 575 """ 576 577 # Work out where the compass is going in WCS coords from the relative location given 578 # Draw the compass 1.5 * the given size in arcsec from that edge of the plot 579 if type(location) == str: 580 RADeg, decDeg=self.wcs.getCentreWCSCoords() 581 x, y=self.wcs.wcs2pix(RADeg, decDeg) 582 halfWidthRADeg, halfHeightDecDeg=self.wcs.getHalfSizeDeg() 583 compassDistRADeg=halfWidthRADeg-1.5*sizeArcSec/3600.0 584 compassDistDecDeg=halfHeightDecDeg-1.5*sizeArcSec/3600.0 585 if self.wcs.isFlipped() == True: 586 compassDistRADeg=compassDistRADeg*-1 587 foundLocation=False 588 if location.find("N") != -1: 589 y=y+compassDistDecDeg/self.wcs.getPixelSizeDeg() 590 foundLocation=True 591 if location.find("S") != -1: 592 y=y-compassDistDecDeg/self.wcs.getPixelSizeDeg() 593 foundLocation=True 594 if location.find("E") != -1: 595 x=x-compassDistRADeg/self.wcs.getPixelSizeDeg() 596 foundLocation=True 597 if location.find("W") != -1: 598 x=x+compassDistRADeg/self.wcs.getPixelSizeDeg() 599 foundLocation=True 600 if foundLocation == False: 601 raise Exception, "didn't understand location string for compass (should be e.g. N, S, E, W)." 602 elif type(location) == tuple: 603 x, y=location 604 else: 605 raise Exception, "didn't understand location for compass - should be string or tuple." 606 RADeg, decDeg=self.wcs.pix2wcs(x, y) 607 608 # Size of compass arrows pixels in plot - converted from arcsec 609 sizePix=(sizeArcSec/3600.0)/self.wcs.getPixelSizeDeg() 610 611 alreadyGot=False 612 for p in self.plotObjects: 613 if p['tag'] == "compass": 614 p['x']=[x] 615 p['y']=[y] 616 p['RA']=[RADeg] 617 p['dec']=[decDeg] 618 p['tag']="compass" 619 p['objLabels']=[None] 620 p['symbol']="compass" 621 p['sizePix']=sizePix 622 p['sizeArcSec']=sizeArcSec 623 p['width']=width 624 p['color']=color 625 p['objLabelSize']=fontSize 626 alreadyGot=True 627 628 if alreadyGot == False: 629 self.plotObjects.append({'x': [x], 'y': [y], 'RA': [RADeg], 'dec': [decDeg], 630 'tag': "compass", 'objLabels': [None], 'symbol': "compass", 631 'sizePix': sizePix, 'width': width, 'color': color, 632 'objLabelSize': fontSize, 'sizeArcSec': sizeArcSec}) 633 self.draw()
634 635
636 - def addScaleBar(self, location, sizeArcSec, color = "white", fontSize = 12, \ 637 width = 20.0):
638 """Adds a scale bar to the ImagePlot at the given location ('N', 'NE', 'E', 'SE', 'S', 639 'SW', 'W', or 'NW'). Note these aren't directions on the WCS coordinate grid, they are 640 relative positions on the plot - so N is top centre, NE is top right, SW is bottom right etc.. 641 Alternatively, pixel coordinates (x, y) in the image can be given. 642 643 @type location: string or tuple 644 @param location: location in the plot where the compass is drawn: 645 - string: N, NE, E, SE, S, SW, W or NW 646 - tuple: (x, y) 647 @type sizeArcSec: float 648 @param sizeArcSec: scale length to indicate on the plot in arc seconds 649 @type color: string 650 @param color: any valid matplotlib color string 651 @type fontSize: float 652 @param fontSize: size of font used to label N and E, in points 653 @type width: float 654 @param width: width of arrow used to mark scale 655 656 """ 657 658 # Work out where the scale abr is going in WCS coords from the relative location given 659 if type(location) == str: 660 RADeg, decDeg=self.wcs.getCentreWCSCoords() 661 x, y=self.wcs.wcs2pix(RADeg, decDeg) 662 halfWidthRADeg, halfHeightDecDeg=self.wcs.getHalfSizeDeg() 663 compassDistRADeg=halfWidthRADeg-1.2*sizeArcSec/3600.0 664 compassDistDecDeg=halfHeightDecDeg-0.15*halfHeightDecDeg 665 if self.wcs.isFlipped() == True: 666 compassDistRADeg=compassDistRADeg*-1 667 foundLocation=False 668 if location.find("N") != -1: 669 y=y+compassDistDecDeg/self.wcs.getPixelSizeDeg() 670 foundLocation=True 671 if location.find("S") != -1: 672 y=y-compassDistDecDeg/self.wcs.getPixelSizeDeg() 673 foundLocation=True 674 if location.find("E") != -1: 675 x=x-compassDistRADeg/self.wcs.getPixelSizeDeg() 676 foundLocation=True 677 if location.find("W") != -1: 678 x=x+compassDistRADeg/self.wcs.getPixelSizeDeg() 679 foundLocation=True 680 if foundLocation == False: 681 raise Exception, "didn't understand location string for compass (should be e.g. N, S, E, W)." 682 elif type(location) == tuple: 683 x, y=location 684 else: 685 raise Exception, "didn't understand location for compass - should be string or tuple." 686 RADeg, decDeg=self.wcs.pix2wcs(x, y) 687 688 # Size of scale arrow in pixels on plot - converted from arcsec 689 sizePix=(sizeArcSec/3600.0)/self.wcs.getPixelSizeDeg() 690 691 alreadyGot=False 692 for p in self.plotObjects: 693 if p['tag'] == "scaleBar": 694 p['x']=[x] 695 p['y']=[y] 696 p['RA']=[RADeg] 697 p['dec']=[decDeg] 698 p['tag']="scaleBar" 699 p['objLabels']=[None] 700 p['symbol']="scaleBar" 701 p['sizePix']=sizePix 702 p['sizeArcSec']=sizeArcSec 703 p['width']=width 704 p['color']=color 705 p['objLabelSize']=fontSize 706 alreadyGot=True 707 708 if alreadyGot == False: 709 self.plotObjects.append({'x': [x], 'y': [y], 'RA': [RADeg], 'dec': [decDeg], 710 'tag': "scaleBar", 'objLabels': [None], 'symbol': "scaleBar", 711 'sizePix': sizePix, 'width': width, 'color': color, 712 'objLabelSize': fontSize, 'sizeArcSec': sizeArcSec}) 713 self.draw()
714 715
716 - def calcWCSAxisLabels(self, axesLabels = "decimal"):
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, numpy.zeros(xArray.shape[0], dtype=float)) 768 yWCS=self.wcs.pix2wcs(numpy.zeros(yArray.shape[0], dtype=float), 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 if ras[1] < ras[0]: 819 ra2x=interpolate.interp1d(ras[::-1], xArray[::-1], kind='linear') 820 else: 821 ra2x=interpolate.interp1d(ras, xArray, kind='linear') 822 RADegs=numpy.arange(RAPlotMin, RAPlotMax-360.0-0.0001, -RADegStep) 823 824 decPlotMin=decDegStep*math.modf(decMin/decDegStep)[1] 825 decPlotMax=decDegStep*math.modf(decMax/decDegStep)[1] 826 if decPlotMin < decMin: 827 decPlotMin=decPlotMin+decDegStep 828 if decPlotMax >= decMax: 829 decPlotMax=decPlotMax-decDegStep 830 decDegs=numpy.arange(decPlotMin, decPlotMax+0.0001, decDegStep) 831 832 if key == "major": 833 if axesLabels == "sexagesimal": 834 for r in RADegs: 835 if r < 0: 836 r=r+360.0 837 h, m, s=astCoords.decimal2hms(r, ":").split(":") 838 hInt=int(round(float(h))) 839 if ticsDict[key]['RA']['unit'] == 'h' and (60.0-float(m)) < 0.01: # Check for rounding error 840 hInt=hInt+1 841 if hInt < 10: 842 hString="0"+str(hInt) 843 else: 844 hString=str(hInt) 845 mInt=int(round(float(m))) 846 if ticsDict[key]['RA']['unit'] == 'm' and (60.0-float(s)) < 0.01: # Check for rounding error 847 mInt=mInt+1 848 if mInt < 10: 849 mString="0"+str(mInt) 850 else: 851 mString=str(mInt) 852 sInt=int(round(float(s))) 853 if sInt < 10: 854 sString="0"+str(sInt) 855 else: 856 sString=str(sInt) 857 if ticsDict[key]['RA']['unit'] == 'h': 858 rString=hString+"$^h$" 859 elif ticsDict[key]['RA']['unit'] == 'm': 860 rString=hString+"$^h$"+mString+"$^m$" 861 else: 862 rString=hString+"$^h$"+mString+"$^m$"+sString+"$^s$" 863 RALabels.append(rString) 864 for D in decDegs: 865 d, m, s=astCoords.decimal2dms(D, ":").split(":") 866 dInt=int(round(float(d))) 867 if ticsDict[key]['dec']['unit'] == 'd' and (60.0-float(m)) < 0.01: # Check for rounding error 868 dInt=dInt+1 869 if dInt < 10 and dInt >= 0 and D > 0: 870 dString="+0"+str(dInt) 871 elif dInt > -10 and dInt <= 0 and D < 0: 872 dString="-0"+str(abs(dInt)) 873 elif dInt >= 10: 874 dString="+"+str(dInt) 875 else: 876 dString=str(dInt) 877 mInt=int(round(float(m))) 878 if ticsDict[key]['dec']['unit'] == 'm' and (60.0-float(s)) < 0.01: # Check for rounding error 879 mInt=mInt+1 880 if mInt < 10: 881 mString="0"+str(mInt) 882 else: 883 mString=str(mInt) 884 sInt=int(round(float(s))) 885 if sInt < 10: 886 sString="0"+str(sInt) 887 else: 888 sString=str(sInt) 889 if ticsDict[key]['dec']['unit'] == 'd': 890 dString=dString+DEG 891 elif ticsDict[key]['dec']['unit'] == 'm': 892 dString=dString+DEG+mString+PRIME 893 else: 894 dString=dString+DEG+mString+PRIME+sString+DOUBLE_PRIME 895 decLabels.append(dString) 896 elif axesLabels == "decimal": 897 if wrappedRA == False: 898 RALabels=RALabels+RADegs.tolist() 899 else: 900 nonNegativeLabels=[] 901 for r in RADegs: 902 if r < 0: 903 r=r+360.0 904 nonNegativeLabels.append(r) 905 RALabels=RALabels+nonNegativeLabels 906 decLabels=decLabels+decDegs.tolist() 907 908 # Format RALabels, decLabels to same number of d.p. 909 dps=[] 910 for r in RALabels: 911 dps.append(len(str(r).split(".")[-1])) 912 dpNumRA=int(math.ceil(numpy.array(dps).mean())) 913 for i in range(len(RALabels)): 914 fString="%."+str(dpNumRA)+"f" 915 RALabels[i]=fString % (RALabels[i]) 916 dps=[] 917 for d in decLabels: 918 dps.append(len(str(d).split(".")[-1])) 919 dpNumDec=int(math.ceil(numpy.array(dps).mean())) 920 for i in range(len(decLabels)): 921 fString="%."+str(dpNumDec)+"f" 922 decLabels[i]=fString % (decLabels[i]) 923 924 if key == 'minor': 925 RALabels=RALabels+RADegs.shape[0]*[''] 926 decLabels=decLabels+decDegs.shape[0]*[''] 927 928 RALocs=RALocs+ra2x(RADegs).tolist() 929 decLocs=decLocs+dec2y(decDegs).tolist() 930 931 self.ticsRA=[RALocs, RALabels] 932 self.ticsDec=[decLocs, decLabels]
933 934
935 - def save(self, fileName):
936 """Saves the ImagePlot in any format that matplotlib can understand, as determined from the 937 fileName extension. 938 939 @type fileName: string 940 @param fileName: path where plot will be written 941 942 """ 943 944 pylab.draw() 945 pylab.savefig(fileName)
946 947
948 - def getTickSteps(self):
949 """Chooses the appropriate WCS coordinate tick steps for the plot based on its size. 950 Whether the ticks are decimal or sexagesimal is set by self.axesLabels. 951 952 Note: minor ticks not used at the moment. 953 954 @rtype: dictionary 955 @return: tick step sizes for major, minor plot ticks, in format {'major', 'minor'} 956 957 """ 958 959 # Aim for 5 major tick marks on a plot 960 xArray=numpy.arange(0, self.data.shape[1], 1) 961 yArray=numpy.arange(0, self.data.shape[0], 1) 962 xWCS=self.wcs.pix2wcs(xArray, numpy.zeros(xArray.shape[0], dtype=float)) 963 yWCS=self.wcs.pix2wcs(numpy.zeros(yArray.shape[0], dtype=float), yArray) 964 xWCS=numpy.array(xWCS) 965 yWCS=numpy.array(yWCS) 966 ras=xWCS[:,0] 967 decs=yWCS[:,1] 968 RAEdges=numpy.array([ras[0], ras[-1]]) 969 RAMin=RAEdges.min() 970 RAMax=RAEdges.max() 971 decMin=decs.min() 972 decMax=decs.max() 973 974 # Work out if wrapped around 975 midRAPix, midDecPix=self.wcs.wcs2pix((RAEdges[1]+RAEdges[0])/2.0, (decMax+decMin)/2.0) 976 if midRAPix < 0 or midRAPix > self.wcs.header['NAXIS1']: 977 wrappedRA=True 978 else: 979 wrappedRA=False 980 if wrappedRA == False: 981 RAWidthDeg=RAMax-RAMin 982 else: 983 RAWidthDeg=(360.0-RAMax)+RAMin 984 decHeightDeg=decMax-decMin 985 986 ticsDict={} 987 ticsDict['major']={} 988 ticsDict['minor']={} 989 if self.axesLabels == "sexagesimal": 990 991 matchIndex = 0 992 for i in range(len(RA_TICK_STEPS)): 993 if RAWidthDeg/2.5 > RA_TICK_STEPS[i]['deg']: 994 matchIndex = i 995 996 ticsDict['major']['RA']=RA_TICK_STEPS[matchIndex] 997 ticsDict['minor']['RA']=RA_TICK_STEPS[matchIndex-1] 998 999 matchIndex = 0 1000 for i in range(len(DEC_TICK_STEPS)): 1001 if decHeightDeg/2.5 > DEC_TICK_STEPS[i]['deg']: 1002 matchIndex = i 1003 1004 ticsDict['major']['dec']=DEC_TICK_STEPS[matchIndex] 1005 ticsDict['minor']['dec']=DEC_TICK_STEPS[matchIndex-1] 1006 1007 return ticsDict 1008 1009 elif self.axesLabels == "decimal": 1010 1011 matchIndex = 0 1012 for i in range(len(DECIMAL_TICK_STEPS)): 1013 if RAWidthDeg/2.5 > DECIMAL_TICK_STEPS[i]: 1014 matchIndex = i 1015 1016 ticsDict['major']['RA']=DECIMAL_TICK_STEPS[matchIndex] 1017 ticsDict['minor']['RA']=DECIMAL_TICK_STEPS[matchIndex-1] 1018 1019 matchIndex = 0 1020 for i in range(len(DECIMAL_TICK_STEPS)): 1021 if decHeightDeg/2.5 > DECIMAL_TICK_STEPS[i]: 1022 matchIndex = i 1023 1024 ticsDict['major']['dec']=DECIMAL_TICK_STEPS[matchIndex] 1025 ticsDict['minor']['dec']=DECIMAL_TICK_STEPS[matchIndex-1] 1026 1027 return ticsDict 1028 1029 else: 1030 raise Exception, "axesLabels must be either 'sexagesimal' or 'decimal'"
1031