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

Source Code for Module astLib.astPlots

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