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   
  46  if sys.version < '3': 
  47      import codecs 
  49          return codecs.unicode_escape_decode(x)[0] 
   50  else: 
  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   
  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           
 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               
 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           
 237          self.plotObjects=[]  
 238           
 239           
 240          self.contourOverlays=[] 
 241           
 242          self.draw() 
  243   
 244   
 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                   
 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       
 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           
 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           
 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           
 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   
 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           
 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           
 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           
 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           
 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           
 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:  
 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:  
 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:  
 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:  
 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                   
 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           
 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           
 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           
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