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-2012 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 293 westPoint,eastPoint,southPoint,northPoint=astCoords.calcRADecSearchBox(ra, dec, p['sizeArcSec']/3600.0/2.0) 294 northPix=self.wcs.wcs2pix(ra, northPoint) 295 eastPix=self.wcs.wcs2pix(eastPoint, dec) 296 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 316 westPoint,eastPoint,southPoint,northPoint=astCoords.calcRADecSearchBox(ra, dec, p['sizeArcSec']/3600.0/2.0) 317 northPix=self.wcs.wcs2pix(ra, northPoint) 318 eastPix=self.wcs.wcs2pix(eastPoint, dec) 319 edx=eastPix[0]-x 320 edy=eastPix[1]-y 321 ndx=northPix[0]-x 322 ndy=northPix[1]-y 323 324 eArrow=patches.Arrow(x, y, edx, edy, edgecolor=p['color'], facecolor=p['color'], width=p['width']) 325 wArrow=patches.Arrow(x, y, -edx, edy, edgecolor=p['color'], facecolor=p['color'], width=p['width']) 326 327 self.axes.add_patch(eArrow) 328 self.axes.add_patch(wArrow) 329 330 # Work out label 331 scaleLabel=None 332 if p['sizeArcSec'] < 60.0: 333 scaleLabel="%.0f %s" % (p['sizeArcSec'], DOUBLE_PRIME) 334 elif p['sizeArcSec'] >= 60.0 and p['sizeArcSec'] < 3600.0: 335 scaleLabel="%.0f %s" % (p['sizeArcSec']/60.0, PRIME) 336 else: 337 scaleLabel="%.0f %s" % (p['sizeArcSec']/3600.0, DEG) 338 339 pylab.text(x, y+0.025*self.data.shape[1], scaleLabel, horizontalalignment='center', 340 verticalalignment='center', fontsize=p['objLabelSize'], color=p['color']) 341 342 if self.axesLabels != None: 343 pylab.xticks(self.ticsRA[0], self.ticsRA[1], weight='normal', family=self.axesFontFamily, \ 344 fontsize=self.axesFontSize) 345 pylab.yticks(self.ticsDec[0], self.ticsDec[1], weight='normal', family=self.axesFontFamily, \ 346 fontsize=self.axesFontSize) 347 pylab.xlabel(self.RAAxisLabel, family=self.axesFontFamily, fontsize=self.axesFontSize) 348 pylab.ylabel(self.decAxisLabel, family=self.axesFontFamily, fontsize=self.axesFontSize) 349 else: 350 pylab.xticks([], []) 351 pylab.yticks([], []) 352 pylab.xlabel("") 353 pylab.ylabel("") 354 355 if self.flipXAxis == False: 356 pylab.xlim(0, self.data.shape[1]-1) 357 else: 358 pylab.xlim(self.data.shape[1]-1, 0) 359 if self.flipYAxis == False: 360 pylab.ylim(0, self.data.shape[0]-1) 361 else: 362 pylab.ylim(self.data.shape[0]-1, 0)
363 364
365 - def addContourOverlay(self, contourImageData, contourWCS, tag, levels = ["linear", "min", "max", 5], 366 width = 1, color = "white", smooth = 0, highAccuracy = False):
367 """Adds image data to the ImagePlot as a contour overlay. The contours can be removed using 368 L{removeContourOverlay}. If a contour overlay already exists with this tag, it will be replaced. 369 370 @type contourImageData: numpy array 371 @param contourImageData: image data array from which contours are to be generated 372 @type contourWCS: astWCS.WCS 373 @param contourWCS: astWCS.WCS object for the image to be contoured 374 @type tag: string 375 @param tag: identifying tag for this set of contours 376 @type levels: list 377 @param levels: sets the contour levels - available options: 378 - values: contourLevels=[list of values specifying each level] 379 - linear spacing: contourLevels=['linear', min level value, max level value, number 380 of levels] - can use "min", "max" to automatically set min, max levels from image data 381 - log spacing: contourLevels=['log', min level value, max level value, number of 382 levels] - can use "min", "max" to automatically set min, max levels from image data 383 @type width: int 384 @param width: width of the overlaid contours 385 @type color: string 386 @param color: color of the overlaid contours, specified by the name of a standard 387 matplotlib color, e.g., "black", "white", "cyan" 388 etc. (do "help(pylab.colors)" in the Python interpreter to see available options) 389 @type smooth: float 390 @param smooth: standard deviation (in arcsec) of Gaussian filter for 391 pre-smoothing of contour image data (set to 0 for no smoothing) 392 @type highAccuracy: bool 393 @param highAccuracy: if True, sample every corresponding pixel in each image; otherwise, sample 394 every nth pixel, where n = the ratio of the image scales. 395 396 """ 397 398 if self.rgbImage == True: 399 backgroundData=self.data[:,:,0] 400 else: 401 backgroundData=self.data 402 contourData=astImages.generateContourOverlay(backgroundData, self.wcs, contourImageData, \ 403 contourWCS, levels, smooth, highAccuracy = highAccuracy) 404 405 alreadyGot=False 406 for c in self.contourOverlays: 407 if c['tag'] == tag: 408 c['contourData']=contourData 409 c['tag']=tag 410 c['color']=color 411 c['width']=width 412 alreadyGot=True 413 414 if alreadyGot == False: 415 self.contourOverlays.append({'contourData': contourData, 'tag': tag, 'color': color, \ 416 'width': width}) 417 self.draw()
418 419
420 - def removeContourOverlay(self, tag):
421 """Removes the contourOverlay from the ImagePlot corresponding to the tag. 422 423 @type tag: string 424 @param tag: tag for contour overlay in ImagePlot.contourOverlays to be removed 425 426 """ 427 428 index=0 429 for p in self.contourOverlays: 430 if p['tag'] == tag: 431 self.plotObjects.remove(self.plotObjects[index]) 432 index=index+1 433 self.draw()
434 435
436 - def addPlotObjects(self, objRAs, objDecs, tag, symbol="circle", size=4.0, width=1.0, color="yellow", 437 objLabels = None, objLabelSize = 12.0):
438 """Add objects with RA, dec coords objRAs, objDecs to the ImagePlot. Only objects that fall within 439 the image boundaries will be plotted. 440 441 symbol specifies the type of symbol with which to mark the object in the image. The following 442 values are allowed: 443 - "circle" 444 - "box" 445 - "cross" 446 - "diamond" 447 448 size specifies the diameter in arcsec of the symbol (if plotSymbol == "circle"), or the width 449 of the box in arcsec (if plotSymbol == "box") 450 451 width specifies the thickness of the symbol lines in pixels 452 453 color can be any valid matplotlib color (e.g. "red", "green", etc.) 454 455 The objects can be removed from the plot by using removePlotObjects(), and then calling 456 draw(). If the ImagePlot already has a set of plotObjects with the same tag, they will be 457 replaced. 458 459 @type objRAs: numpy array or list 460 @param objRAs: object RA coords in decimal degrees 461 @type objDecs: numpy array or list 462 @param objDecs: corresponding object Dec. coords in decimal degrees 463 @type tag: string 464 @param tag: identifying tag for this set of objects 465 @type symbol: string 466 @param symbol: either "circle", "box", "cross", or "diamond" 467 @type size: float 468 @param size: size of symbols to plot (radius in arcsec, or width of box) 469 @type width: float 470 @param width: width of symbols in pixels 471 @type color: string 472 @param color: any valid matplotlib color string, e.g. "red", "green" etc. 473 @type objLabels: list 474 @param objLabels: text labels to plot next to objects in figure 475 @type objLabelSize: float 476 @param objLabelSize: size of font used for object labels (in points) 477 478 """ 479 480 pixCoords=self.wcs.wcs2pix(objRAs, objDecs) 481 482 xMax=self.data.shape[1] 483 yMax=self.data.shape[0] 484 485 if objLabels == None: 486 objLabels=[None]*len(objRAs) 487 488 xInPlot=[] 489 yInPlot=[] 490 RAInPlot=[] 491 decInPlot=[] 492 labelInPlot=[] 493 for p, r, d, l in zip(pixCoords, objRAs, objDecs, objLabels): 494 if p[0] >= 0 and p[0] < xMax and p[1] >= 0 and p[1] < yMax: 495 xInPlot.append(p[0]) 496 yInPlot.append(p[1]) 497 RAInPlot.append(r) 498 decInPlot.append(d) 499 labelInPlot.append(l) 500 501 xInPlot=numpy.array(xInPlot) 502 yInPlot=numpy.array(yInPlot) 503 RAInPlot=numpy.array(RAInPlot) 504 decInPlot=numpy.array(decInPlot) 505 506 # Size of symbols in pixels in plot - converted from arcsec 507 sizePix=(size/3600.0)/self.wcs.getPixelSizeDeg() 508 509 alreadyGot=False 510 for p in self.plotObjects: 511 if p['tag'] == tag: 512 p['x']=xInPlot 513 p['y']=yInPlot 514 p['RA']=RAInPlot 515 p['dec']=decInPlot 516 p['tag']=tag 517 p['objLabels']=objLabels 518 p['symbol']=symbol 519 p['sizePix']=sizePix 520 p['sizeArcSec']=size 521 p['width']=width 522 p['color']=color 523 p['objLabelSize']=objLabelSize 524 alreadyGot=True 525 526 if alreadyGot == False: 527 self.plotObjects.append({'x': xInPlot, 'y': yInPlot, 'RA': RAInPlot, 'dec': decInPlot, 528 'tag': tag, 'objLabels': labelInPlot, 'symbol': symbol, 529 'sizePix': sizePix, 'width': width, 'color': color, 530 'objLabelSize': objLabelSize, 'sizeArcSec': size}) 531 self.draw()
532 533
534 - def removePlotObjects(self, tag):
535 """Removes the plotObjects from the ImagePlot corresponding to the tag. The plot must be redrawn 536 for the change to take effect. 537 538 @type tag: string 539 @param tag: tag for set of objects in ImagePlot.plotObjects to be removed 540 541 """ 542 543 index=0 544 for p in self.plotObjects: 545 if p['tag'] == tag: 546 self.plotObjects.remove(self.plotObjects[index]) 547 index=index+1 548 self.draw()
549 550
551 - def addCompass(self, location, sizeArcSec, color = "white", fontSize = 12, \ 552 width = 20.0):
553 """Adds a compass to the ImagePlot at the given location ('N', 'NE', 'E', 'SE', 'S', 554 'SW', 'W', or 'NW'). Note these aren't directions on the WCS coordinate grid, they are 555 relative positions on the plot - so N is top centre, NE is top right, SW is bottom right etc.. 556 Alternatively, pixel coordinates (x, y) in the image can be given. 557 558 @type location: string or tuple 559 @param location: location in the plot where the compass is drawn: 560 - string: N, NE, E, SE, S, SW, W or NW 561 - tuple: (x, y) 562 @type sizeArcSec: float 563 @param sizeArcSec: length of the compass arrows on the plot in arc seconds 564 @type color: string 565 @param color: any valid matplotlib color string 566 @type fontSize: float 567 @param fontSize: size of font used to label N and E, in points 568 @type width: float 569 @param width: width of arrows used to mark compass 570 571 """ 572 573 if type(location) == str: 574 cRADeg, cDecDeg=self.wcs.getCentreWCSCoords() 575 RAMin, RAMax, decMin, decMax=self.wcs.getImageMinMaxWCSCoords() 576 westPoint,eastPoint,southPoint,northPoint=astCoords.calcRADecSearchBox(cRADeg, cDecDeg, sizeArcSec/3600.0/2.0) 577 sizeRADeg=eastPoint-westPoint 578 sizeDecDeg=northPoint-southPoint 579 xSizePix=(sizeArcSec/3600.0)/self.wcs.getXPixelSizeDeg() 580 ySizePix=(sizeArcSec/3600.0)/self.wcs.getYPixelSizeDeg() 581 X=self.data.shape[1] 582 Y=self.data.shape[0] 583 xBufferPix=0.5*xSizePix 584 yBufferPix=0.5*ySizePix 585 cx, cy=self.wcs.wcs2pix(cRADeg, cDecDeg) 586 foundLocation=False 587 x=cy 588 y=cx 589 if self.wcs.isFlipped() == False: 590 if location.find("N") != -1: 591 y=Y-2*yBufferPix 592 foundLocation=True 593 if location.find("S") != -1: 594 y=yBufferPix 595 foundLocation=True 596 if location.find("E") != -1: 597 x=xBufferPix*2 598 foundLocation=True 599 if location.find("W") != -1: 600 x=X-xBufferPix 601 foundLocation=True 602 else: 603 if location.find("S") != -1: 604 y=Y-2*yBufferPix 605 foundLocation=True 606 if location.find("N") != -1: 607 y=yBufferPix 608 foundLocation=True 609 if location.find("W") != -1: 610 x=xBufferPix*2 611 foundLocation=True 612 if location.find("E") != -1: 613 x=X-xBufferPix 614 foundLocation=True 615 if foundLocation == False: 616 raise Exception, "didn't understand location string for scale bar (should be e.g. N, S, E, W)." 617 RADeg, decDeg=self.wcs.pix2wcs(x, y) 618 elif type(location) == tuple or type(location) == list: 619 x, y=location 620 RADeg, decDeg=self.wcs.pix2wcs(x, y) 621 else: 622 raise Exception, "didn't understand location for scale bar - should be string or tuple." 623 624 alreadyGot=False 625 for p in self.plotObjects: 626 if p['tag'] == "compass": 627 p['x']=[x] 628 p['y']=[y] 629 p['RA']=[RADeg] 630 p['dec']=[decDeg] 631 p['tag']="compass" 632 p['objLabels']=[None] 633 p['symbol']="compass" 634 p['sizeArcSec']=sizeArcSec 635 p['width']=width 636 p['color']=color 637 p['objLabelSize']=fontSize 638 alreadyGot=True 639 640 if alreadyGot == False: 641 self.plotObjects.append({'x': [x], 'y': [y], 'RA': [RADeg], 'dec': [decDeg], 642 'tag': "compass", 'objLabels': [None], 'symbol': "compass", 643 'width': width, 'color': color, 644 'objLabelSize': fontSize, 'sizeArcSec': sizeArcSec}) 645 self.draw()
646 647
648 - def addScaleBar(self, location, sizeArcSec, color = "white", fontSize = 12, \ 649 width = 20.0):
650 """Adds a scale bar to the ImagePlot at the given location ('N', 'NE', 'E', 'SE', 'S', 651 'SW', 'W', or 'NW'). Note these aren't directions on the WCS coordinate grid, they are 652 relative positions on the plot - so N is top centre, NE is top right, SW is bottom right etc.. 653 Alternatively, pixel coordinates (x, y) in the image can be given. 654 655 @type location: string or tuple 656 @param location: location in the plot where the compass is drawn: 657 - string: N, NE, E, SE, S, SW, W or NW 658 - tuple: (x, y) 659 @type sizeArcSec: float 660 @param sizeArcSec: scale length to indicate on the plot in arc seconds 661 @type color: string 662 @param color: any valid matplotlib color string 663 @type fontSize: float 664 @param fontSize: size of font used to label N and E, in points 665 @type width: float 666 @param width: width of arrow used to mark scale 667 668 """ 669 670 # Work out where the scale bar is going in WCS coords from the relative location given 671 if type(location) == str: 672 cRADeg, cDecDeg=self.wcs.getCentreWCSCoords() 673 RAMin, RAMax, decMin, decMax=self.wcs.getImageMinMaxWCSCoords() 674 westPoint,eastPoint,southPoint,northPoint=astCoords.calcRADecSearchBox(cRADeg, cDecDeg, sizeArcSec/3600.0/2.0) 675 sizeRADeg=eastPoint-westPoint 676 sizeDecDeg=northPoint-southPoint 677 xSizePix=(sizeArcSec/3600.0)/self.wcs.getXPixelSizeDeg() 678 ySizePix=(sizeArcSec/3600.0)/self.wcs.getYPixelSizeDeg() 679 X=self.data.shape[1] 680 Y=self.data.shape[0] 681 xBufferPix=0.6*ySizePix 682 yBufferPix=0.05*Y 683 cx, cy=self.wcs.wcs2pix(cRADeg, cDecDeg) 684 foundLocation=False 685 x=cy 686 y=cx 687 if self.wcs.isFlipped() == False: 688 if location.find("N") != -1: 689 y=Y-1.5*yBufferPix 690 foundLocation=True 691 if location.find("S") != -1: 692 y=yBufferPix 693 foundLocation=True 694 if location.find("E") != -1: 695 x=xBufferPix 696 foundLocation=True 697 if location.find("W") != -1: 698 x=X-xBufferPix 699 foundLocation=True 700 else: 701 if location.find("S") != -1: 702 y=Y-1.5*yBufferPix 703 foundLocation=True 704 if location.find("N") != -1: 705 y=yBufferPix 706 foundLocation=True 707 if location.find("W") != -1: 708 x=xBufferPix 709 foundLocation=True 710 if location.find("E") != -1: 711 x=X-xBufferPix 712 foundLocation=True 713 if foundLocation == False: 714 raise Exception, "didn't understand location string for scale bar (should be e.g. N, S, E, W)." 715 RADeg, decDeg=self.wcs.pix2wcs(x, y) 716 elif type(location) == tuple or type(location) == list: 717 x, y=location 718 RADeg, decDeg=self.wcs.pix2wcs(x, y) 719 else: 720 raise Exception, "didn't understand location for scale bar - should be string or tuple." 721 722 alreadyGot=False 723 for p in self.plotObjects: 724 if p['tag'] == "scaleBar": 725 p['x']=[x] 726 p['y']=[y] 727 p['RA']=[RADeg] 728 p['dec']=[decDeg] 729 p['tag']="scaleBar" 730 p['objLabels']=[None] 731 p['symbol']="scaleBar" 732 p['sizeArcSec']=sizeArcSec 733 p['width']=width 734 p['color']=color 735 p['objLabelSize']=fontSize 736 alreadyGot=True 737 738 if alreadyGot == False: 739 self.plotObjects.append({'x': [x], 'y': [y], 'RA': [RADeg], 'dec': [decDeg], 740 'tag': "scaleBar", 'objLabels': [None], 'symbol': "scaleBar", 741 'width': width, 'color': color, 742 'objLabelSize': fontSize, 'sizeArcSec': sizeArcSec}) 743 self.draw()
744 745
746 - def calcWCSAxisLabels(self, axesLabels = "decimal"):
747 """This function calculates the positions of coordinate labels for the RA and Dec axes of the 748 ImagePlot. The tick steps are calculated automatically unless self.RATickSteps, 749 self.decTickSteps are set to values other than "auto" (see L{ImagePlot.__init__}). 750 751 The ImagePlot must be redrawn for changes to be applied. 752 753 @type axesLabels: string 754 @param axesLabels: either "sexagesimal" (for H:M:S, D:M:S), "decimal" (for decimal degrees), 755 or None for no coordinate axes labels 756 757 """ 758 759 # Label equinox on axes 760 equinox=self.wcs.getEquinox() 761 if equinox<1984: 762 equinoxLabel="B"+str(int(equinox)) 763 else: 764 equinoxLabel="J"+str(int(equinox)) 765 766 self.axesLabels=axesLabels 767 768 ticsDict=self.getTickSteps() 769 770 # Manual override - note: no minor tick marks anymore, but may want to bring them back 771 if self.RATickSteps != "auto": 772 ticsDict['major']['RA']=self.RATickSteps 773 if self.decTickSteps != "auto": 774 ticsDict['major']['dec']=self.decTickSteps 775 776 RALocs=[] 777 decLocs=[] 778 RALabels=[] 779 decLabels=[] 780 key="major" 781 #for key in ticsDict.keys(): # key is major or minor 782 if self.axesLabels == "sexagesimal": 783 self.RAAxisLabel="R.A. ("+equinoxLabel+")" 784 self.decAxisLabel="Dec. ("+equinoxLabel+")" 785 RADegStep=ticsDict[key]['RA']['deg'] 786 decDegStep=ticsDict[key]['dec']['deg'] 787 elif self.axesLabels == "decimal": 788 self.RAAxisLabel="R.A. Degrees ("+equinoxLabel+")" 789 self.decAxisLabel="Dec. Degrees ("+equinoxLabel+")" 790 RADegStep=ticsDict[key]['RA'] 791 decDegStep=ticsDict[key]['dec'] 792 else: 793 raise Exception, "axesLabels must be either 'sexagesimal' or 'decimal'" 794 795 xArray=numpy.arange(0, self.data.shape[1], 1) 796 yArray=numpy.arange(0, self.data.shape[0], 1) 797 xWCS=self.wcs.pix2wcs(xArray, numpy.zeros(xArray.shape[0], dtype=float)) 798 yWCS=self.wcs.pix2wcs(numpy.zeros(yArray.shape[0], dtype=float), yArray) 799 xWCS=numpy.array(xWCS) 800 yWCS=numpy.array(yWCS) 801 ras=xWCS[:,0] 802 decs=yWCS[:,1] 803 RAEdges=numpy.array([ras[0], ras[-1]]) 804 RAMin=RAEdges.min() 805 RAMax=RAEdges.max() 806 decMin=decs.min() 807 decMax=decs.max() 808 809 # Work out if wrapped around 810 midRAPix, midDecPix=self.wcs.wcs2pix((RAEdges[1]+RAEdges[0])/2.0, (decMax+decMin)/2.0) 811 if midRAPix < 0 or midRAPix > self.wcs.header['NAXIS1']: 812 wrappedRA=True 813 else: 814 wrappedRA=False 815 816 # Note RA, dec work in opposite sense below because E at left 817 if ras[1] < ras[0]: 818 self.flipXAxis=False 819 ra2x=interpolate.interp1d(ras[::-1], xArray[::-1], kind='linear') 820 else: 821 self.flipXAxis=True 822 ra2x=interpolate.interp1d(ras, xArray, kind='linear') 823 if decs[1] < decs[0]: 824 self.flipYAxis=True 825 dec2y=interpolate.interp1d(decs[::-1], yArray[::-1], kind='linear') 826 else: 827 self.flipYAxis=False 828 dec2y=interpolate.interp1d(decs, yArray, kind='linear') 829 830 if wrappedRA == False: 831 RAPlotMin=RADegStep*math.modf(RAMin/RADegStep)[1] 832 RAPlotMax=RADegStep*math.modf(RAMax/RADegStep)[1] 833 if RAPlotMin < RAMin: 834 RAPlotMin=RAPlotMin+RADegStep 835 if RAPlotMax >= RAMax: 836 RAPlotMax=RAPlotMax-RADegStep 837 RADegs=numpy.arange(RAPlotMin, RAPlotMax+0.0001, RADegStep) 838 else: 839 RAPlotMin=RADegStep*math.modf(RAMin/RADegStep)[1] 840 RAPlotMax=RADegStep*math.modf(RAMax/RADegStep)[1] 841 if RAPlotMin > RAMin: 842 RAPlotMin=RAPlotMin-RADegStep 843 if RAPlotMax <= RAMax: 844 RAPlotMax=RAPlotMax+RADegStep 845 for i in range(ras.shape[0]): 846 if ras[i] >= RAMax and ras[i] <= 360.0: 847 ras[i]=ras[i]-360.0 848 if ras[1] < ras[0]: 849 ra2x=interpolate.interp1d(ras[::-1], xArray[::-1], kind='linear') 850 else: 851 ra2x=interpolate.interp1d(ras, xArray, kind='linear') 852 RADegs=numpy.arange(RAPlotMin, RAPlotMax-360.0-0.0001, -RADegStep) 853 854 decPlotMin=decDegStep*math.modf(decMin/decDegStep)[1] 855 decPlotMax=decDegStep*math.modf(decMax/decDegStep)[1] 856 if decPlotMin < decMin: 857 decPlotMin=decPlotMin+decDegStep 858 if decPlotMax >= decMax: 859 decPlotMax=decPlotMax-decDegStep 860 decDegs=numpy.arange(decPlotMin, decPlotMax+0.0001, decDegStep) 861 862 if key == "major": 863 if axesLabels == "sexagesimal": 864 for r in RADegs: 865 if r < 0: 866 r=r+360.0 867 h, m, s=astCoords.decimal2hms(r, ":").split(":") 868 hInt=int(round(float(h))) 869 if ticsDict[key]['RA']['unit'] == 'h' and (60.0-float(m)) < 0.01: # Check for rounding error 870 hInt=hInt+1 871 if hInt < 10: 872 hString="0"+str(hInt) 873 else: 874 hString=str(hInt) 875 mInt=int(round(float(m))) 876 if ticsDict[key]['RA']['unit'] == 'm' and (60.0-float(s)) < 0.01: # Check for rounding error 877 mInt=mInt+1 878 if mInt < 10: 879 mString="0"+str(mInt) 880 else: 881 mString=str(mInt) 882 sInt=int(round(float(s))) 883 if sInt < 10: 884 sString="0"+str(sInt) 885 else: 886 sString=str(sInt) 887 if ticsDict[key]['RA']['unit'] == 'h': 888 rString=hString+"$^h$" 889 elif ticsDict[key]['RA']['unit'] == 'm': 890 rString=hString+"$^h$"+mString+"$^m$" 891 else: 892 rString=hString+"$^h$"+mString+"$^m$"+sString+"$^s$" 893 RALabels.append(rString) 894 for D in decDegs: 895 d, m, s=astCoords.decimal2dms(D, ":").split(":") 896 dInt=int(round(float(d))) 897 if ticsDict[key]['dec']['unit'] == 'd' and (60.0-float(m)) < 0.01: # Check for rounding error 898 dInt=dInt+1 899 if dInt < 10 and dInt >= 0 and D > 0: 900 dString="+0"+str(dInt) 901 elif dInt > -10 and dInt <= 0 and D < 0: 902 dString="-0"+str(abs(dInt)) 903 elif dInt >= 10: 904 dString="+"+str(dInt) 905 else: 906 dString=str(dInt) 907 mInt=int(round(float(m))) 908 if ticsDict[key]['dec']['unit'] == 'm' and (60.0-float(s)) < 0.01: # Check for rounding error 909 mInt=mInt+1 910 if mInt < 10: 911 mString="0"+str(mInt) 912 else: 913 mString=str(mInt) 914 sInt=int(round(float(s))) 915 if sInt < 10: 916 sString="0"+str(sInt) 917 else: 918 sString=str(sInt) 919 if ticsDict[key]['dec']['unit'] == 'd': 920 dString=dString+DEG 921 elif ticsDict[key]['dec']['unit'] == 'm': 922 dString=dString+DEG+mString+PRIME 923 else: 924 dString=dString+DEG+mString+PRIME+sString+DOUBLE_PRIME 925 decLabels.append(dString) 926 elif axesLabels == "decimal": 927 if wrappedRA == False: 928 RALabels=RALabels+RADegs.tolist() 929 else: 930 nonNegativeLabels=[] 931 for r in RADegs: 932 if r < 0: 933 r=r+360.0 934 nonNegativeLabels.append(r) 935 RALabels=RALabels+nonNegativeLabels 936 decLabels=decLabels+decDegs.tolist() 937 938 # Format RALabels, decLabels to same number of d.p. 939 dps=[] 940 for r in RALabels: 941 dps.append(len(str(r).split(".")[-1])) 942 dpNumRA=int(math.ceil(numpy.array(dps).mean())) 943 for i in range(len(RALabels)): 944 fString="%."+str(dpNumRA)+"f" 945 RALabels[i]=fString % (RALabels[i]) 946 dps=[] 947 for d in decLabels: 948 dps.append(len(str(d).split(".")[-1])) 949 dpNumDec=int(math.ceil(numpy.array(dps).mean())) 950 for i in range(len(decLabels)): 951 fString="%."+str(dpNumDec)+"f" 952 decLabels[i]=fString % (decLabels[i]) 953 954 if key == 'minor': 955 RALabels=RALabels+RADegs.shape[0]*[''] 956 decLabels=decLabels+decDegs.shape[0]*[''] 957 958 RALocs=RALocs+ra2x(RADegs).tolist() 959 decLocs=decLocs+dec2y(decDegs).tolist() 960 961 self.ticsRA=[RALocs, RALabels] 962 self.ticsDec=[decLocs, decLabels]
963 964
965 - def save(self, fileName):
966 """Saves the ImagePlot in any format that matplotlib can understand, as determined from the 967 fileName extension. 968 969 @type fileName: string 970 @param fileName: path where plot will be written 971 972 """ 973 974 pylab.draw() 975 pylab.savefig(fileName)
976 977
978 - def getTickSteps(self):
979 """Chooses the appropriate WCS coordinate tick steps for the plot based on its size. 980 Whether the ticks are decimal or sexagesimal is set by self.axesLabels. 981 982 Note: minor ticks not used at the moment. 983 984 @rtype: dictionary 985 @return: tick step sizes for major, minor plot ticks, in format {'major', 'minor'} 986 987 """ 988 989 # Aim for 5 major tick marks on a plot 990 xArray=numpy.arange(0, self.data.shape[1], 1) 991 yArray=numpy.arange(0, self.data.shape[0], 1) 992 xWCS=self.wcs.pix2wcs(xArray, numpy.zeros(xArray.shape[0], dtype=float)) 993 yWCS=self.wcs.pix2wcs(numpy.zeros(yArray.shape[0], dtype=float), yArray) 994 xWCS=numpy.array(xWCS) 995 yWCS=numpy.array(yWCS) 996 ras=xWCS[:,0] 997 decs=yWCS[:,1] 998 RAEdges=numpy.array([ras[0], ras[-1]]) 999 RAMin=RAEdges.min() 1000 RAMax=RAEdges.max() 1001 decMin=decs.min() 1002 decMax=decs.max() 1003 1004 # Work out if wrapped around 1005 midRAPix, midDecPix=self.wcs.wcs2pix((RAEdges[1]+RAEdges[0])/2.0, (decMax+decMin)/2.0) 1006 if midRAPix < 0 or midRAPix > self.wcs.header['NAXIS1']: 1007 wrappedRA=True 1008 else: 1009 wrappedRA=False 1010 if wrappedRA == False: 1011 RAWidthDeg=RAMax-RAMin 1012 else: 1013 RAWidthDeg=(360.0-RAMax)+RAMin 1014 decHeightDeg=decMax-decMin 1015 1016 ticsDict={} 1017 ticsDict['major']={} 1018 ticsDict['minor']={} 1019 if self.axesLabels == "sexagesimal": 1020 1021 matchIndex = 0 1022 for i in range(len(RA_TICK_STEPS)): 1023 if RAWidthDeg/2.5 > RA_TICK_STEPS[i]['deg']: 1024 matchIndex = i 1025 1026 ticsDict['major']['RA']=RA_TICK_STEPS[matchIndex] 1027 ticsDict['minor']['RA']=RA_TICK_STEPS[matchIndex-1] 1028 1029 matchIndex = 0 1030 for i in range(len(DEC_TICK_STEPS)): 1031 if decHeightDeg/2.5 > DEC_TICK_STEPS[i]['deg']: 1032 matchIndex = i 1033 1034 ticsDict['major']['dec']=DEC_TICK_STEPS[matchIndex] 1035 ticsDict['minor']['dec']=DEC_TICK_STEPS[matchIndex-1] 1036 1037 return ticsDict 1038 1039 elif self.axesLabels == "decimal": 1040 1041 matchIndex = 0 1042 for i in range(len(DECIMAL_TICK_STEPS)): 1043 if RAWidthDeg/2.5 > DECIMAL_TICK_STEPS[i]: 1044 matchIndex = i 1045 1046 ticsDict['major']['RA']=DECIMAL_TICK_STEPS[matchIndex] 1047 ticsDict['minor']['RA']=DECIMAL_TICK_STEPS[matchIndex-1] 1048 1049 matchIndex = 0 1050 for i in range(len(DECIMAL_TICK_STEPS)): 1051 if decHeightDeg/2.5 > DECIMAL_TICK_STEPS[i]: 1052 matchIndex = i 1053 1054 ticsDict['major']['dec']=DECIMAL_TICK_STEPS[matchIndex] 1055 ticsDict['minor']['dec']=DECIMAL_TICK_STEPS[matchIndex-1] 1056 1057 return ticsDict 1058 1059 else: 1060 raise Exception, "axesLabels must be either 'sexagesimal' or 'decimal'"
1061