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