1
2 """module for producing astronomical plots
3
4 (c) 2007-2009 Matt Hilton
5
6 U{http://astlib.sourceforge.net}
7
8 This module provides the matplotlib powered ImagePlot class, which is designed to be flexible.
9 ImagePlots can have RA, Dec. coordinate axes, contour overlays, and have objects marked in them,
10 using WCS coordinates. RGB plots are supported too.
11
12 @var DEC_TICK_STEPS: Defines the possible coordinate label steps on the delination axis in
13 sexagesimal mode. Dictionary format: {'deg', 'int', 'unit'}
14 @type DEC_TICK_STEPS: dictionary list
15
16 @var RA_TICK_STEPS: Defines the possible coordinate label steps on the right ascension axis in
17 sexagesimal mode. Dictionary format: {'deg', 'int', 'unit'}
18 @type RA_TICK_STEPS: dictionary list
19
20 @var DECIMAL_TICK_STEPS: Defines the possible coordinate label steps on both coordinate axes in
21 decimal degrees mode.
22 @type DECIMAL_TICK_STEPS: list
23
24 @var DEG: Variable to stand in for the degrees symbol.
25 @type DEG: string
26
27 @var PRIME: Variable to stand in for the prime symbol.
28 @type PRIME: string
29
30 @var DOUBLE_PRIME: Variable to stand in for the double prime symbol.
31 @type DOUBLE_PRIME: string
32
33 """
34
35 import math
36 import astImages
37 import astWCS
38 import astCoords
39 import numpy
40 import pyfits
41 try:
42 import pylab
43 import matplotlib.patches as patches
44 except:
45 print "WARNING: astPlots: failed to import matplotlib - some functions will not work."
46 import sys
47
48 DEC_TICK_STEPS=[{'deg': 1.0/60.0/60.0, 'int': 1, 'unit': "s"},
49 {'deg': 5.0/60.0/60.0, 'int': 5, 'unit': "s"},
50 {'deg': 10.0/60.0/60.0, 'int': 10, 'unit': "s"},
51 {'deg': 30.0/60.0/60.0, 'int': 30, 'unit': "s"},
52 {'deg': 1.0/60.0, 'int': 1, 'unit': "m"},
53 {'deg': 5.0/60.0, 'int': 5, 'unit': "m"},
54 {'deg': 15.0/60.0, 'int': 15, 'unit': "m"},
55 {'deg': 30.0/60.0, 'int': 30, 'unit': "m"},
56 {'deg': 1.0, 'int': 1, 'unit': "d"},
57 {'deg': 5.0, 'int': 5, 'unit': "d"},
58 {'deg': 10.0, 'int': 10, 'unit': "d"},
59 {'deg': 30.0, 'int': 30, 'unit': "d"}]
60
61 RA_TICK_STEPS=[ {'deg': (1.0/60.0/60.0/24.0)*360.0, 'int': 1, 'unit': "s"},
62 {'deg': (5.0/60.0/60.0/24.0)*360.0, 'int': 5, 'unit': "s"},
63 {'deg': (10.0/60.0/60.0/24.0)*360.0, 'int': 10, 'unit': "s"},
64 {'deg': (30.0/60.0/60.0/24.0)*360.0, 'int': 30, 'unit': "s"},
65 {'deg': (1.0/60.0/24.0)*360.0, 'int': 1, 'unit': "m"},
66 {'deg': (5.0/60.0/24.0)*360.0, 'int': 5, 'unit': "m"},
67 {'deg': (10.0/60.0/24.0)*360.0, 'int': 15, 'unit': "m"},
68 {'deg': (30.0/60.0/24.0)*360.0, 'int': 30, 'unit': "m"},
69 {'deg': (1.0/24.0)*360.0, 'int': 1, 'unit': "h"},
70 {'deg': (3.0/24.0)*360.0, 'int': 3, 'unit': "h"},
71 {'deg': (6.0/24.0)*360.0, 'int': 6, 'unit': "h"},
72 {'deg': (12.0/24.0)*360.0, 'int': 12, 'unit': "h"}]
73
74 DECIMAL_TICK_STEPS=[0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 90.0]
75
76 DEG = u"\N{DEGREE SIGN}"
77 PRIME = "\'"
78 DOUBLE_PRIME = "\""
79
80
82 """This class describes a matplotlib image plot containing an astronomical image with an
83 associated WCS.
84
85 Objects within the image boundaries can be marked by passing their WCS coordinates to
86 L{ImagePlot.addPlotObjects}.
87
88 Other images can be overlaid using L{ImagePlot.addContourOverlay}.
89
90 For images rotated with North at the top, East at the left (as can be done using
91 L{astImages.clipRotatedImageSectionWCS}), WCS coordinate axes can be plotted, with tic marks
92 set appropriately for the image size (note that this may not currently be 100% reliable for
93 sexagesimal coordinates). Otherwise, a compass can be plotted showing the directions of North
94 and East in the image.
95
96 RGB images are also supported.
97
98 The plot can of course be tweaked further after creation using matplotlib/pylab commands.
99
100 """
101 - def __init__(self, imageData, imageWCS, axes = [0.1,0.1,0.8,0.8], \
102 cutLevels = ["smart", 99.5], colorMapName = "gray", title = None, axesLabels = "decimal", \
103 minorLabels = False, axesFontFamily="serif", axesFontSize=12.0, colorBar = False):
104 """Makes an ImagePlot from the given image array and astWCS. For coordinate axes to work, the
105 image and WCS should have been rotated such that East is at the left, North is at the top
106 (see e.g. L{astImages.clipRotatedImageSectionWCS}).
107
108 If imageData is given as a list in the format [r, g, b], an color RGB plot will be made. However,
109 in this case the cutLevels must be specified manually for each component as a list -
110 i.e. cutLevels = [[r min, r max], [g min, g max], [b min, b max]]. In this case of course, the
111 colorMap will be ignored. All r, g, b image arrays must have the same dimensions.
112
113 Set axesLabels = None to make a plot without coordinate axes plotted.
114
115 The axes can be marked in either sexagesimal or decimal celestial coordinates: the appropriate
116 axis scales will be determined automatically from the size of the image array and associated
117 WCS.
118
119 @type imageData: numpy array or list
120 @param imageData: image data array or list of numpy arrays [r, g, b]
121 @type imageWCS: astWCS.WCS
122 @param imageWCS: astWCS.WCS object
123 @type axes: list
124 @param axes: specifies where in the current figure to draw the finder chart (see pylab.axes)
125 @type cutLevels: list
126 @param cutLevels: sets the image scaling - available options:
127 - pixel values: cutLevels=[low value, high value].
128 - histogram equalisation: cutLevels=["histEq", number of bins ( e.g. 1024)]
129 - relative: cutLevels=["relative", cut per cent level (e.g. 99.5)]
130 - smart: cutLevels=["smart", cut per cent level (e.g. 99.5)]
131 ["smart", 99.5] seems to provide good scaling over a range of different images.
132 Note that for RGB images, cut levels must be specified manually i.e. as a list:
133 [[r min, rmax], [g min, g max], [b min, b max]]
134 @type colorMapName: string
135 @param colorMapName: name of a standard matplotlib colormap, e.g. "hot", "cool", "gray"
136 etc. (do "help(pylab.colormaps)" in the Python interpreter to see available options)
137 @type title: string
138 @param title: optional title for the plot
139 @type axesLabels: string
140 @param axesLabels: either "sexagesimal" (for H:M:S, D:M:S), "decimal" (for decimal degrees)
141 or None (for no coordinate axes labels)
142 @type minorLabels: bool
143 @param minorLabels: if set to True, add additional labels between coordinate labels
144 @type axesFontFamily: string
145 @param axesFontFamily: matplotlib fontfamily, e.g. 'serif', 'sans-serif' etc.
146 @type axesFontSize: float
147 @param axesFontSize: font size of axes labels and titles (in points)
148 @type colorBar: bool
149 @param colorBar: if True, plot a vertical color bar at the side of the image indicating the intensity
150 scale.
151
152 """
153
154 self.RADeg, self.decDeg=imageWCS.getCentreWCSCoords()
155 self.wcs=imageWCS
156
157
158 if type(imageData) == list:
159 if len(imageData) == 3:
160 if len(cutLevels) == 3:
161 r=astImages.normalise(imageData[0], cutLevels[0])
162 g=astImages.normalise(imageData[1], cutLevels[1])
163 b=astImages.normalise(imageData[2], cutLevels[2])
164 rgb=numpy.array([r.transpose(), g.transpose(), b.transpose()])
165 rgb=rgb.transpose()
166 self.data=rgb
167 self.rgbImage=True
168 else:
169 raise Exception, "tried to create a RGB array, but cutLevels is not a list of 3 lists"
170
171 else:
172 raise Exception, "tried to create a RGB array but imageData is not a list of 3 arrays"
173 else:
174 self.data=imageData
175 self.rgbImage=False
176
177 self.axes=pylab.axes(axes)
178 self.cutLevels=cutLevels
179 self.colorMapName=colorMapName
180 self.title=title
181 self.axesLabels=axesLabels
182 self.minorLabels=minorLabels
183 self.colorBar=colorBar
184 self.axesFontSize=axesFontSize
185 self.axesFontFamily=axesFontFamily
186
187 if self.axesLabels != None:
188 self.calcWCSAxisLabels(axesLabels = self.axesLabels, minorLabels = self.minorLabels)
189
190
191 self.plotObjects=[]
192
193
194 self.contourOverlays=[]
195
196 self.draw()
197
198
200 """Redraws the ImagePlot - this should be called after adding plot objects, compass, contours
201 etc.
202
203 """
204
205 pylab.axes(self.axes)
206 pylab.cla()
207
208 if self.title != None:
209 pylab.title(self.title)
210 try:
211 colorMap=pylab.cm.get_cmap(self.colorMapName)
212 except AssertionError:
213 raise Exception, self.colorMapName+"is not a defined matplotlib colormap."
214
215 if self.rgbImage == False:
216 self.cutImage=astImages.intensityCutImage(self.data, self.cutLevels)
217 if self.cutLevels[0]=="histEq":
218 pylab.imshow(self.cutImage['image'], interpolation="bilinear", origin='lower', cmap=colorMap)
219 else:
220 pylab.imshow(self.cutImage['image'], interpolation="bilinear", norm=self.cutImage['norm'], \
221 origin='lower', cmap=colorMap)
222 else:
223 pylab.imshow(self.data, interpolation="bilinear", origin='lower')
224
225 if self.colorBar == True:
226 pylab.colorbar(shrink=0.8)
227
228 for c in self.contourOverlays:
229 pylab.contour(c['contourData']['scaledImage'], c['contourData']['contourLevels'],
230 colors=c['color'], linewidths=c['width'])
231
232 for p in self.plotObjects:
233 for x, y, l in zip(p['x'], p['y'], p['objLabels']):
234 if p['symbol'] == "circle":
235 c=patches.Circle((x, y), radius=p['sizePix']/2.0, fill=False, edgecolor=p['color'],
236 linewidth=p['width'])
237 self.axes.add_patch(c)
238 elif p['symbol'] == "box":
239 c=patches.Rectangle((x-p['sizePix']/2, y-p['sizePix']/2), p['sizePix'], p['sizePix'],
240 fill=False, edgecolor=p['color'], linewidth=p['width'])
241 self.axes.add_patch(c)
242 elif p['symbol'] == "cross":
243 pylab.plot([x-p['sizePix']/2, x+p['sizePix']/2], [y, y], linestyle='-',
244 linewidth=p['width'], color= p['color'])
245 pylab.plot([x, x], [y-p['sizePix']/2, y+p['sizePix']/2], linestyle='-',
246 linewidth=p['width'], color= p['color'])
247 if l != None:
248 pylab.text(x, y+p['sizePix']/1.5, l, horizontalalignment='center', \
249 fontsize=p['objLabelSize'], color=p['color'])
250
251 if p['symbol'] == "compass":
252 x=p['x'][0]
253 y=p['y'][0]
254 ra=p['RA'][0]
255 dec=p['dec'][0]
256 northPoint=dec+p['sizeArcSec']/3600.0
257 eastPoint=ra+p['sizeArcSec']/3600.0
258 sizePix=(p['sizeArcSec']/3600.0)/self.wcs.getPixelSizeDeg()
259 northPix=self.wcs.wcs2pix(ra, northPoint)
260 eastPix=self.wcs.wcs2pix(eastPoint, dec)
261 edx=eastPix[0]-x
262 edy=eastPix[1]-y
263 ndx=northPix[0]-x
264 ndy=northPix[1]-y
265 nArrow=patches.Arrow(x, y, ndx, ndy, edgecolor=p['color'], facecolor=p['color'], width=p['width'])
266 eArrow=patches.Arrow(x, y, edx, edy, edgecolor=p['color'], facecolor=p['color'], width=p['width'])
267 self.axes.add_patch(nArrow)
268 self.axes.add_patch(eArrow)
269 pylab.text(x+ndx+ndx*0.2, y+ndy+ndy*0.2, "N", horizontalalignment='center',
270 verticalalignment='center', fontsize=p['objLabelSize'], color=p['color'])
271 pylab.text(x+edx+edx*0.2, y+edy+edy*0.2, "E", horizontalalignment='center',
272 verticalalignment='center', fontsize=p['objLabelSize'], color=p['color'])
273
274 if self.axesLabels != None:
275 pylab.xticks(self.ticsRA[0], self.ticsRA[1], weight='normal', family=self.axesFontFamily, \
276 fontsize=self.axesFontSize)
277 pylab.yticks(self.ticsDec[0], self.ticsDec[1], weight='normal', family=self.axesFontFamily, \
278 fontsize=self.axesFontSize)
279 pylab.xlabel(self.RAAxisLabel, family=self.axesFontFamily, fontsize=self.axesFontSize)
280 pylab.ylabel(self.decAxisLabel, family=self.axesFontFamily, fontsize=self.axesFontSize)
281 else:
282 pylab.xticks([], [])
283 pylab.yticks([], [])
284 pylab.xlabel("")
285 pylab.ylabel("")
286
287 pylab.xlim(0, self.data.shape[1]-1)
288 pylab.ylim(0, self.data.shape[0]-1)
289
290
291 - def addContourOverlay(self, contourImageData, contourWCS, label, levels = ['linear', 'min', 'max', 5], \
292 width = 1, color = "white", smooth = 0):
293 """Adds image data to the ImagePlot as a contour overlay. The contours will be plotted when
294 ImagePlot.draw() is next called. The contours can be removed using L{removeContourOverlay}
295 If a contour overlay already exists with this label, it will be replaced.
296
297 @type contourImageData: numpy array
298 @param contourImageData: image data array from which contours are to be generated
299 @type contourWCS: astWCS.WCS
300 @param contourWCS: astWCS.WCS object for the image to be contoured
301 @type label: string
302 @param label: identifying label for this set of contours
303 @type levels: list
304 @param levels: sets the contour levels - available options:
305 - values: contourLevels=[list of values specifying each level]
306 - linear spacing: contourLevels=['linear', min level value, max level value, number
307 of levels]
308 - log spacing: contourLevels=['log', min level value, max level value, number of
309 levels]
310 @type width: int
311 @param width: width of the overlaid contours
312 @type color: string
313 @param color: color of the overlaid contours, specified by the name of a standard
314 matplotlib color, e.g., "black", "white", "cyan"
315 etc. (do "help(pylab.colors)" in the Python interpreter to see available options)
316 @type smooth: float
317 @param smooth: standard deviation (in pixels) of Gaussian filter for
318 pre-smoothing of contour image data (set to 0 for no smoothing)
319
320 """
321
322 if self.rgbImage == True:
323 backgroundData=self.data[:,:,0]
324 else:
325 backgroundData=self.data
326 contourData=astImages.generateContourOverlay(backgroundData, self.wcs, contourImageData, \
327 contourWCS, levels, smooth)
328
329 alreadyGot=False
330 for c in self.contourOverlays:
331 if c['label'] == label:
332 c['contourData']=contourData
333 c['label']=label
334 c['color']=color
335 c['width']=width
336 alreadyGot=True
337
338 if alreadyGot == False:
339 self.contourOverlays.append({'contourData': contourData, 'label': label, 'color': color, \
340 'width': width})
341
342
344 """Removes the contourOverlay from the ImagePlot corresponding to the label. The plot must be redrawn
345 for the change to take effect.
346
347 @type label: string
348 @param label: label for contour overlay in ImagePlot.contourOverlays to be removed
349
350 """
351
352 index=0
353 for p in self.contourOverlays:
354 if p['label'] == label:
355 self.plotObjects.remove(self.plotObjects[index])
356 index=index+1
357
358
359 - def addPlotObjects(self, objRAs, objDecs, label, symbol="circle", size=4.0, width=1.0, color="yellow",
360 objLabels = None, objLabelSize = 12.0):
361 """Add objects with RA, dec coords objRAs, objDecs to the ImagePlot. The objects will be plotted
362 when ImagePlot.draw() is next called; only objects that fall within the image boundaries will be
363 plotted.
364
365 symbol specifies the type of symbol with which to mark the object in the image. The following
366 values are allowed:
367 - "circle"
368 - "box"
369 - "cross"
370
371 size specifies the diameter in arcsec of the symbol (if plotSymbol == "circle"), or the width
372 of the box in arcsec (if plotSymbol == "box")
373
374 width specifies the thickness of the symbol lines in pixels
375
376 color can be any valid matplotlib color (e.g. "red", "green", etc.)
377
378 The objects can be removed from the plot by using removePlotObjects(), and then calling
379 draw(). If the ImagePlot already has a set of plotObjects with the same label, they will be
380 replaced.
381
382 @type objRAs: numpy array or list
383 @param objRAs: object RA coords in decimal degrees
384 @type objDecs: numpy array or list
385 @param objDecs: corresponding object Dec. coords in decimal degrees
386 @type label: string
387 @param label: identifying label for this set of objects
388 @type symbol: string
389 @param symbol: either "circle" or "box"
390 @type size: float
391 @param size: size of symbols to plot (radius in arcsec, or width of box)
392 @type width: float
393 @param width: width of symbols in pixels
394 @type color: string
395 @param color: any valid matplotlib color string, e.g. "red", "green" etc.
396 @type objLabels: list
397 @param objLabels: text labels to plot next to objects in figure
398 @type objLabelSize: float
399 @param objLabelSize: size of font used for object labels (in points)
400
401 """
402
403 pixCoords=self.wcs.wcs2pix(objRAs, objDecs)
404
405 xMax=self.data.shape[1]
406 yMax=self.data.shape[0]
407
408 if objLabels == None:
409 objLabels=[None]*len(objRAs)
410
411 xInPlot=[]
412 yInPlot=[]
413 RAInPlot=[]
414 decInPlot=[]
415 labelInPlot=[]
416 for p, r, d, l in zip(pixCoords, objRAs, objDecs, objLabels):
417 if p[0] >= 0 and p[0] < xMax and p[1] >= 0 and p[1] < yMax:
418 xInPlot.append(p[0])
419 yInPlot.append(p[1])
420 RAInPlot.append(r)
421 decInPlot.append(d)
422 labelInPlot.append(l)
423
424 xInPlot=numpy.array(xInPlot)
425 yInPlot=numpy.array(yInPlot)
426 RAInPlot=numpy.array(RAInPlot)
427 decInPlot=numpy.array(decInPlot)
428
429
430 sizePix=(size/3600.0)/self.wcs.getPixelSizeDeg()
431
432 alreadyGot=False
433 for p in self.plotObjects:
434 if p['label'] == label:
435 p['x']=xInPlot
436 p['y']=yInPlot
437 p['RA']=RAInPlot
438 p['dec']=decInPlot
439 p['label']=label
440 p['objLabels']=objLabels
441 p['symbol']=symbol
442 p['sizePix']=sizePix
443 p['sizeArcSec']=size
444 p['width']=width
445 p['color']=color
446 p['objLabelSize']=objLabelSize
447 alreadyGot=True
448
449 if alreadyGot == False:
450 self.plotObjects.append({'x': xInPlot, 'y': yInPlot, 'RA': RAInPlot, 'dec': decInPlot,
451 'label': label, 'objLabels': labelInPlot, 'symbol': symbol,
452 'sizePix': sizePix, 'width': width, 'color': color,
453 'objLabelSize': objLabelSize, 'sizeArcSec': size})
454
455
457 """Removes the plotObjects from the ImagePlot corresponding to the label. The plot must be redrawn
458 for the change to take effect.
459
460 @type label: string
461 @param label: label for set of objects in ImagePlot.plotObjects to be removed
462
463 """
464
465 index=0
466 for p in self.plotObjects:
467 if p['label'] == label:
468 self.plotObjects.remove(self.plotObjects[index])
469 index=index+1
470
471
472 - def addCompass(self, location, sizeArcSec, color = "white", fontSize = 12, \
473 width = 20.0):
474 """Adds a compass to the ImagePlot at the given location ('N', 'NE', 'E', 'SE', 'S',
475 'SW', 'W', or 'NW'). Note these aren't directions on the WCS coordinate grid, they are
476 relative positions on the plot - so N is top centre, NE is top right, SW is bottom right etc..
477 Alternatively, pixel coordinates (x, y) in the image can be given.
478
479 @type location: string or tuple
480 @param location: location in the plot where the compass is drawn:
481 - string: N, NE, E, SE, S, SW, W or NW
482 - tuple: (x, y)
483 @type sizeArcSec: float
484 @param sizeArcSec: length of the compass arrows on the plot in arc seconds
485 @type color: string
486 @param color: any valid matplotlib color string
487 @type fontSize: float
488 @param fontSize: size of font used to label N and E, in points
489 @type width: float
490 @param width: width of arrows used to mark compass
491
492 """
493
494
495
496 if type(location) == str:
497 RADeg, decDeg=self.wcs.getCentreWCSCoords()
498 x, y=self.wcs.wcs2pix(RADeg, decDeg)
499 halfWidthRADeg, halfHeightDecDeg=self.wcs.getHalfSizeDeg()
500 compassDistRADeg=halfWidthRADeg-3.0*sizeArcSec/3600.0
501 compassDistDecDeg=halfHeightDecDeg-3.0*sizeArcSec/3600.0
502 if self.wcs.isFlipped() == True:
503 compassDistRADeg=compassDistRADeg*-1
504 foundLocation=False
505 if location.find("N") != -1:
506 y=y+compassDistDecDeg/self.wcs.getPixelSizeDeg()
507 foundLocation=True
508 if location.find("S") != -1:
509 y=y-compassDistDecDeg/self.wcs.getPixelSizeDeg()
510 foundLocation=True
511 if location.find("E") != -1:
512 x=x-compassDistRADeg/self.wcs.getPixelSizeDeg()
513 foundLocation=True
514 if location.find("W") != -1:
515 x=x+compassDistRADeg/self.wcs.getPixelSizeDeg()
516 foundLocation=True
517 if foundLocation == False:
518 raise Exception, "didn't understand location string for compass (should be e.g. N, S, E, W)."
519 elif type(location) == tuple:
520 x, y=location
521 else:
522 raise Exception, "didn't understand location for compass - should be string or tuple."
523 RADeg, decDeg=self.wcs.pix2wcs(x, y)
524
525
526 sizePix=(sizeArcSec/3600.0)/self.wcs.getPixelSizeDeg()
527
528 alreadyGot=False
529 for p in self.plotObjects:
530 if p['label'] == "compass":
531 p['x']=[x]
532 p['y']=[y]
533 p['RA']=[RADeg]
534 p['dec']=[decDeg]
535 p['label']="compass"
536 p['objLabels']=[None]
537 p['symbol']="compass"
538 p['sizePix']=sizePix
539 p['sizeArcSec']=sizeArcSec
540 p['width']=width
541 p['color']=color
542 p['objLabelSize']=fontSize
543 alreadyGot=True
544
545 if alreadyGot == False:
546 self.plotObjects.append({'x': [x], 'y': [y], 'RA': [RADeg], 'dec': [decDeg],
547 'label': "compass", 'objLabels': [None], 'symbol': "compass",
548 'sizePix': sizePix, 'width': width, 'color': color,
549 'objLabelSize': fontSize, 'sizeArcSec': sizeArcSec})
550
551
553 """
554 This function calculates the positions of coordinate labels for the RA and Dec axes of the
555 ImagePlot. The ImagePlot must be redrawn for changes to be applied.
556
557 @type axesLabels: string
558 @param axesLabels: either "sexagesimal" (for H:M:S, D:M:S), "decimal" (for decimal degrees),
559 or None for no coordinate axes labels
560 @type minorLabels: bool
561 @param minorLabels: if set to True, add additional labels between coordinate labels
562
563 """
564
565 self.axesLabels=axesLabels
566 self.minorLabels=minorLabels
567
568
569 equinox=self.wcs.getEquinox()
570 if equinox<1984:
571 equinoxLabel="B"+str(int(equinox))
572 else:
573 equinoxLabel="J"+str(int(equinox))
574
575
576 topLeft=self.wcs.pix2wcs(0,0)
577 bottomRight=self.wcs.pix2wcs(self.data.shape[1], self.data.shape[0])
578
579
580 ticLabelListRA=[]
581 ticLocListRA=[]
582 ticLabelListDec=[]
583 ticLocListDec=[]
584
585
586 hmsRALeft=astCoords.decimal2hms(topLeft[0], ":")
587 hmsRARight=astCoords.decimal2hms(bottomRight[0], ":")
588 if topLeft[0] > bottomRight[0]:
589 raDegDiff=abs(topLeft[0]-bottomRight[0])
590 else:
591
592 raDegDiff=(360.0-bottomRight[0])+topLeft[0]
593 tickMarkSteps=getTickSteps(raDegDiff, "ra")
594 bitsRight=hmsRARight.split(":")
595 bitsLeft=hmsRALeft.split(":")
596 raMajorTickStep=tickMarkSteps['major']
597 raMinorTickStep=tickMarkSteps['minor']
598 raUnmarkedTickStep=tickMarkSteps['unlabelled']
599
600
601 dmsDecTop=astCoords.decimal2dms(topLeft[1], ":")
602 dmsDecBottom=astCoords.decimal2dms(bottomRight[1], ":")
603 decDegDiff=abs(topLeft[1]-bottomRight[1])
604 tickMarkSteps=getTickSteps(decDegDiff, "dec")
605 bitsTop=dmsDecTop.split(":")
606 bitsBottom=dmsDecBottom.split(":")
607 decMajorTickStep=tickMarkSteps['major']
608 decMinorTickStep=tickMarkSteps['minor']
609 decUnmarkedTickStep=tickMarkSteps['unlabelled']
610
611 if self.axesLabels == "sexagesimal":
612
613 xAxisLabel="R.A. ("+equinoxLabel+")"
614 yAxisLabel="Dec. ("+equinoxLabel+")"
615
616 hRange=range(int(bitsRight[0])-2, int(bitsLeft[0])+2)
617
618 if bitsLeft[0] < bitsRight[0]:
619 hRange=range(int(bitsRight[0]), 24)+range(int(bitsLeft[0]),int(bitsLeft[0])+1)
620 mRange=range(int(bitsRight[1]), int(bitsLeft[1])+2)
621 if len(hRange)>1:
622 mRange=range(0, 60)
623 sRange=range(0, 60)
624 for h in hRange:
625 hString=str(h)
626 if abs(h)<10:
627 hString="0"+hString
628 for m in mRange:
629 mString=str(m)
630 if m<10:
631 mString="0"+str(m)
632 for s in sRange:
633 sString=str(s)+".000"
634 if s<10:
635 sString="0"+str(s)+".000"
636 hmsMark=hString+":"+mString+":"+sString
637 degMark=astCoords.hms2decimal(hmsMark, ":")
638 pixMark=self.wcs.wcs2pix(degMark, bottomRight[1])
639 if pixMark[0] > 0 and pixMark[0] < self.data.shape[1]:
640 addedLabel=False
641 if eval(raMajorTickStep['unit']+" % "+str(raMajorTickStep['int'])) == 0:
642 if raMajorTickStep['unit'] == "h" and m == 0 and s == 0 :
643 label=hmsMark.split(":")[0]+".0"
644 ticLabelListRA.append(label)
645 ticLocListRA.append(int(round(pixMark[0])))
646 addedLabel=True
647 elif raMajorTickStep['unit'] == "m" and s == 0:
648 label=hmsMark.split(":")[0]+":"+hmsMark.split(":")[1]+".0"
649 ticLabelListRA.append(label)
650 ticLocListRA.append(int(round(pixMark[0])))
651 addedLabel=True
652 elif raMajorTickStep['unit'] == "s":
653 label=hmsMark.split(":")[0]+":"+hmsMark.split(":")[1]+":"+hmsMark.split(":")[2][:-4]+".0"
654 ticLabelListRA.append(label)
655 ticLocListRA.append(int(round(pixMark[0])))
656 addedLabel=True
657
658 if self.minorLabels == True:
659 if addedLabel == False and eval(raMinorTickStep['unit']+" % "+str(raMinorTickStep['int'])) == 0:
660 if raMinorTickStep['unit'] == "h" and m == 0 and s == 0 :
661 label=""
662 ticLabelListRA.append(label)
663 ticLocListRA.append(int(round(pixMark[0])))
664 addedLabel=True
665 elif raMinorTickStep['unit'] == "m" and s == 0:
666 label=hmsMark.split(":")[1]+".0"
667 ticLabelListRA.append(label)
668 ticLocListRA.append(int(round(pixMark[0])))
669 addedLabel=True
670 elif raMinorTickStep['unit'] == "s":
671 label=hmsMark.split(":")[2][:-4]+".0"
672 ticLabelListRA.append(label)
673 ticLocListRA.append(int(round(pixMark[0])))
674 addedLabel=True
675
676 if addedLabel == False and eval(raUnmarkedTickStep['unit']+" % "+str(raUnmarkedTickStep['int'])) == 0:
677 ticLabelListRA.append("")
678 ticLocListRA.append(int(round(pixMark[0])))
679
680
681 dRange=range(int(bitsBottom[0])-2, int(bitsTop[0])+2)
682 if 0 in dRange:
683 dRange.insert(dRange.index(0), 0)
684 minusZero=True
685 mRange=range(int(bitsBottom[1]), int(bitsTop[1])+2)
686 if len(dRange)>1:
687 mRange=range(0, 60)
688 sRange=range(0, 60)
689 for d in dRange:
690 if d<0:
691 dString=str(d)
692 elif d==0:
693 if minusZero == True:
694 dString="-"+str(d)
695 minusZero=False
696 else:
697 dString="+"+str(d)
698 else:
699 dString="+"+str(d)
700 if abs(d)<10:
701 dString=dString[:1]+"0"+dString[1:]
702
703 for m in mRange:
704 mString=str(m)
705 if m<10:
706 mString="0"+str(m)
707 for s in sRange:
708 sString=str(s)+".00"
709 if s<10:
710 sString="0"+str(s)+".00"
711 dmsMark=dString+":"+mString+":"+sString
712 degMark=astCoords.dms2decimal(dmsMark, ":")
713 pixMark=self.wcs.wcs2pix(bottomRight[0], degMark)
714 if pixMark[1] > 0 and pixMark[1] < self.data.shape[0]:
715 addedLabel=False
716 if eval(decMajorTickStep['unit']+" % "+str(decMajorTickStep['int'])) == 0:
717 if decMajorTickStep['unit'] == "d" and m == 0 and s == 0 :
718 if dmsMark.split(":")[0] == "-00":
719 continue
720 else:
721 label=dmsMark.split(":")[0]+DEG+".0"
722 ticLabelListDec.append(label)
723 ticLocListDec.append(int(round(pixMark[1])))
724 addedLabel=True
725 elif decMajorTickStep['unit'] == "m" and s == 0:
726 if dmsMark.split(":")[0] == "-00" and dmsMark.split(":")[1] == "00":
727 continue
728 else:
729 label=dmsMark.split(":")[0]+DEG+dmsMark.split(":")[1]+PRIME+".0"
730 ticLabelListDec.append(label)
731 ticLocListDec.append(int(round(pixMark[1])))
732 addedLabel=True
733 elif decMajorTickStep['unit'] == "s":
734 label=dmsMark.split(":")[0]+DEG+dmsMark.split(":")[1]+PRIME+dmsMark.split(":")[2][:-3]+DOUBLE_PRIME+".0"
735 ticLabelListDec.append(label)
736 ticLocListDec.append(int(round(pixMark[1])))
737 addedLabel=True
738
739 if self.minorLabels == True:
740 if addedLabel == False and eval(decMinorTickStep['unit']+" % "+str(decMinorTickStep['int'])) == 0:
741 if decMinorTickStep['unit'] == "d" and m == 0 and s == 0 :
742 label=""
743 ticLabelListDec.append(label)
744 ticLocListDec.append(int(round(pixMark[1])))
745 addedLabel=True
746 elif decMinorTickStep['unit'] == "m" and s == 0:
747 label=dmsMark.split(":")[1]+PRIME+".0"
748 ticLabelListDec.append(label)
749 ticLocListDec.append(int(round(pixMark[1])))
750 addedLabel=True
751 elif decMinorTickStep['unit'] == "s":
752 label=dmsMark.split(":")[2][:-3]+DOUBLE_PRIME+".0"
753 ticLabelListDec.append(label)
754 ticLocListDec.append(int(round(pixMark[1])))
755 addedLabel=True
756
757 if addedLabel == False and eval(decUnmarkedTickStep['unit']+" % "+str(decUnmarkedTickStep['int'])) == 0:
758 ticLabelListDec.append("")
759 ticLocListDec.append(int(round(pixMark[1])))
760
761 elif self.axesLabels == "decimal":
762
763 xAxisLabel="R.A. Degrees ("+equinoxLabel+")"
764 yAxisLabel="Dec. Degrees ("+equinoxLabel+")"
765
766 if topLeft[0] > bottomRight[0]:
767 raDegDiff=abs(topLeft[0]-bottomRight[0])
768 else:
769
770 raDegDiff=(360.0-bottomRight[0])+topLeft[0]
771 tickMarkSteps=getTickSteps(raDegDiff, None, mode = "decimal")
772 raMajorTickStep=tickMarkSteps['major']
773 raMinorTickStep=tickMarkSteps['minor']
774 raUnmarkedTickStep=tickMarkSteps['unlabelled']
775 raMajorRatio=raMajorTickStep/raUnmarkedTickStep
776 raMinorRatio=raMinorTickStep/raUnmarkedTickStep
777 raMajorFormat="%."+str(len(str(raMajorTickStep)[str(raMajorTickStep).find(".")+1:]))+"f"
778 raMinorFormat="%."+str(len(str(raMinorTickStep)[str(raMinorTickStep).find(".")+1:]))+"f"
779
780
781 decDegDiff=abs(topLeft[1]-bottomRight[1])
782 decMajorTickStep=tickMarkSteps['major']
783 decMinorTickStep=tickMarkSteps['minor']
784 decUnmarkedTickStep=tickMarkSteps['unlabelled']
785 decMajorRatio=decMajorTickStep/decUnmarkedTickStep
786 decMinorRatio=decMinorTickStep/decUnmarkedTickStep
787 decMajorFormat="%."+str(len(str(decMajorTickStep)[str(decMajorTickStep).find(".")+1:]))+"f"
788 decMinorFormat="%."+str(len(str(decMinorTickStep)[str(decMinorTickStep).find(".")+1:]))+"f"
789
790
791 if self.minorLabels == True:
792 raMajorFormat=raMinorFormat
793 decMajorFormat=decMinorFormat
794
795
796 raMin=int(topLeft[0])-2
797 raMax=int(bottomRight[0])+2
798 raSteps=int((raMax-raMin)/raUnmarkedTickStep)
799
800 decMin=int(bottomRight[1])-2
801 decMax=int(topLeft[1])+2
802 decSteps=int((decMax-decMin)/decUnmarkedTickStep)
803
804 for i in range(raSteps):
805 degMark=raMin+i*raUnmarkedTickStep
806 if degMark < 360:
807 pixMark=self.wcs.wcs2pix(degMark, bottomRight[1])
808 if pixMark[0] > 0 and pixMark[0] < self.data.shape[1]:
809 addedLabel=False
810 if i % raMajorRatio == 0:
811 ticLabelListRA.append(raMajorFormat % degMark)
812 ticLocListRA.append(int(round(pixMark[0])))
813 addedLabel=True
814
815 if self.minorLabels == True:
816 if addedLabel == False and i % raMinorRatio == 0:
817 ticLabelListRA.append(raMinorFormat % degMark)
818 ticLocListRA.append(int(round(pixMark[0])))
819 addedLabel=True
820
821 if addedLabel == False:
822 ticLabelListRA.append("")
823 ticLocListRA.append(int(round(pixMark[0])))
824
825 for i in range(decSteps):
826 degMark=decMin+i*decUnmarkedTickStep
827 pixMark=self.wcs.wcs2pix(bottomRight[0], degMark)
828 if pixMark[1] > 0 and pixMark[1] < self.data.shape[0]:
829 addedLabel=False
830 if i % decMajorRatio == 0:
831 ticLabelListDec.append(decMajorFormat % degMark)
832 ticLocListDec.append(int(round(pixMark[1])))
833 addedLabel=True
834
835 if self.minorLabels == True:
836 if addedLabel == False and i % decMinorRatio == 0:
837 ticLabelListDec.append(decMinorFormat % degMark)
838 ticLocListDec.append(int(round(pixMark[1])))
839 addedLabel=True
840
841 if addedLabel == False:
842 ticLabelListDec.append("")
843 ticLocListDec.append(int(round(pixMark[1])))
844
845 self.ticsRA=[ticLocListRA, ticLabelListRA]
846 self.ticsDec=[ticLocListDec, ticLabelListDec]
847 self.RAAxisLabel=xAxisLabel
848 self.decAxisLabel=yAxisLabel
849
850
851 - def save(self, fileName):
852 """Saves the ImagePlot in any format that matplotlib can understand, as determined from the
853 fileName extension.
854
855 @type fileName: string
856 @param fileName: path where plot will be written
857
858 """
859
860 pylab.draw()
861 pylab.savefig(fileName)
862
863
864
866 """Chooses the appropriate WCS coordinate tick steps for the given axis of a plot of given size.
867
868 @type plotSizeDeg: float
869 @param plotSizeDeg: size of plot in decimal degrees
870 @type axis: string
871 @param axis: either "ra" or "dec"
872 @type mode: string
873 @param mode: either "sexagesimal" (for H:M:S, D:M:S) or "decimal" (for decimal degrees)
874 @rtype: dictionary
875 @return: tick step sizes for major, minor and unlabelled plot ticks, in format {'major', 'minor',
876 'unmarked'}
877
878 @note: axis is ignored is mode = "decimal"
879
880 """
881
882
883
884 if mode == "sexagesimal":
885
886 if axis == "ra":
887 matchIndex = 0
888 for i in range(len(RA_TICK_STEPS)):
889 if plotSizeDeg/3.0 > RA_TICK_STEPS[i]['deg']:
890 matchIndex = i
891
892 if matchIndex<2:
893 matchIndex=2
894
895 return {'major': RA_TICK_STEPS[matchIndex], 'minor': RA_TICK_STEPS[matchIndex-1], 'unlabelled': RA_TICK_STEPS[matchIndex-2]}
896
897 elif axis == "dec":
898 matchIndex = 0
899 for i in range(len(DEC_TICK_STEPS)):
900 if plotSizeDeg/3.0 > DEC_TICK_STEPS[i]['deg']:
901 matchIndex = i
902
903 if matchIndex<2:
904 matchIndex=2
905
906 return {'major': DEC_TICK_STEPS[matchIndex], 'minor': DEC_TICK_STEPS[matchIndex-1], 'unlabelled': DEC_TICK_STEPS[matchIndex-2]}
907
908 else:
909 raise Exception, "axis must be 'ra' or 'dec'"
910
911 elif mode == "decimal":
912
913 matchIndex = 0
914 for i in range(len(DECIMAL_TICK_STEPS)):
915 if plotSizeDeg/3.0 > DECIMAL_TICK_STEPS[i]:
916 matchIndex = i
917
918 if matchIndex<2:
919 matchIndex=2
920
921 return {'major': DECIMAL_TICK_STEPS[matchIndex], 'minor': DECIMAL_TICK_STEPS[matchIndex-1], 'unlabelled': DECIMAL_TICK_STEPS[matchIndex-2]}
922
923 else:
924 raise Exception, "mode must be either 'sexagesimal' or 'decimal'"
925
926
927