astLib.astPlots

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

# Source Code for Module astLib.astPlots

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