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

Source Code for Module astLib.astImages

   1  # -*- coding: utf-8 -*- 
   2  """module for simple .fits image tasks (rotation, clipping out sections, making .pngs etc.) 
   3   
   4  (c) 2007-2009 Matt Hilton  
   5   
   6  U{http://astlib.sourceforge.net} 
   7   
   8  Some routines in this module will fail if, e.g., asked to clip a section from a .fits image at a 
   9  position not found within the image (as determined using the WCS). Where this occurs, the function 
  10  will return None. An error message will be printed to the console when this happens if 
  11  astImages.REPORT_ERRORS=True (the default). Testing if an astImages function returns None can be 
  12  used to handle errors in scripts.  
  13   
  14  """ 
  15   
  16  REPORT_ERRORS=True 
  17   
  18  import os 
  19  import sys 
  20  import math 
  21  from astLib import astWCS 
  22  import pyfits 
  23  try: 
  24      from scipy import ndimage 
  25      from scipy import interpolate 
  26  except: 
  27      print "WARNING: astImages: failed to import scipy.ndimage - some functions will not work." 
  28  import numpy 
  29  try: 
  30      import matplotlib 
  31      from matplotlib import pylab 
  32      matplotlib.interactive(False) 
  33  except: 
  34      print "WARNING: astImages: failed to import matplotlib - some functions will not work." 
  35  try: 
  36      import Image 
  37  except: 
  38      print "WARNING: astImages: failed to import Image - some functions will not work." 
  39   
  40  #--------------------------------------------------------------------------------------------------- 
41 -def clipImageSectionWCS(imageData, imageWCS, RADeg, decDeg, clipSizeDeg, returnWCS = True):
42 """Clips a square or rectangular section from an image array at the given celestial coordinates. 43 An updated WCS for the clipped section is optionally returned. 44 45 Note that the clip size is specified in degrees on the sky. For projections that have varying 46 real pixel scale across the map (e.g. CEA), use L{clipUsingRADecCoords} instead. 47 48 @type imageData: numpy array 49 @param imageData: image data array 50 @type imageWCS: astWCS.WCS 51 @param imageWCS: astWCS.WCS object 52 @type RADeg: float 53 @param RADeg: coordinate in decimal degrees 54 @type decDeg: float 55 @param decDeg: coordinate in decimal degrees 56 @type clipSizeDeg: float or list in format [widthDeg, heightDeg] 57 @param clipSizeDeg: if float, size of square clipped section in decimal degrees; if list, 58 size of clipped section in degrees in x, y axes of image respectively 59 @type returnWCS: bool 60 @param returnWCS: if True, return an updated WCS for the clipped section 61 @rtype: dictionary 62 @return: clipped image section (numpy array), updated astWCS WCS object for 63 clipped image section, in format {'data', 'wcs'}. 64 65 """ 66 67 imHeight=imageData.shape[0] 68 imWidth=imageData.shape[1] 69 xImScale=imageWCS.getXPixelSizeDeg() 70 yImScale=imageWCS.getYPixelSizeDeg() 71 72 if type(clipSizeDeg) == float: 73 xHalfClipSizeDeg=clipSizeDeg/2.0 74 yHalfClipSizeDeg=xHalfClipSizeDeg 75 elif type(clipSizeDeg) == list or type(clipSizeDeg) == tuple: 76 xHalfClipSizeDeg=clipSizeDeg[0]/2.0 77 yHalfClipSizeDeg=clipSizeDeg[1]/2.0 78 else: 79 raise Exception, "did not understand clipSizeDeg: should be float, or [widthDeg, heightDeg]" 80 81 xHalfSizePix=xHalfClipSizeDeg/xImScale 82 yHalfSizePix=yHalfClipSizeDeg/yImScale 83 84 cPixCoords=imageWCS.wcs2pix(RADeg, decDeg) 85 86 cTopLeft=[cPixCoords[0]+xHalfSizePix, cPixCoords[1]+yHalfSizePix] 87 cBottomRight=[cPixCoords[0]-xHalfSizePix, cPixCoords[1]-yHalfSizePix] 88 89 X=[int(round(cTopLeft[0])),int(round(cBottomRight[0]))] 90 Y=[int(round(cTopLeft[1])),int(round(cBottomRight[1]))] 91 92 X.sort() 93 Y.sort() 94 95 if X[0] < 0: 96 X[0]=0 97 if X[1] > imWidth: 98 X[1]=imWidth 99 if Y[0] < 0: 100 Y[0]=0 101 if Y[1] > imHeight: 102 Y[1]=imHeight 103 104 clippedData=imageData[Y[0]:Y[1],X[0]:X[1]] 105 106 # Update WCS 107 if returnWCS == True: 108 try: 109 oldCRPIX1=imageWCS.header['CRPIX1'] 110 oldCRPIX2=imageWCS.header['CRPIX2'] 111 clippedWCS=imageWCS.copy() 112 clippedWCS.header.update('NAXIS1', clippedData.shape[1]) 113 clippedWCS.header.update('NAXIS2', clippedData.shape[0]) 114 clippedWCS.header.update('CRPIX1', oldCRPIX1-X[0]) 115 clippedWCS.header.update('CRPIX2', oldCRPIX2-Y[0]) 116 clippedWCS.updateFromHeader() 117 118 except KeyError: 119 120 if REPORT_ERRORS == True: 121 122 print "WARNING: astImages.clipImageSectionWCS() : no CRPIX1, CRPIX2 keywords found - not updating clipped image WCS." 123 124 clippedData=imageData[Y[0]:Y[1],X[0]:X[1]] 125 clippedWCS=imageWCS.copy() 126 else: 127 clippedWCS=None 128 129 return {'data': clippedData, 'wcs': clippedWCS}
130 131 #---------------------------------------------------------------------------------------------------
132 -def clipImageSectionPix(imageData, XCoord, YCoord, clipSizePix):
133 """Clips a square or rectangular section from an image array at the given pixel coordinates. 134 135 @type imageData: numpy array 136 @param imageData: image data array 137 @type XCoord: float 138 @param XCoord: coordinate in pixels 139 @type YCoord: float 140 @param YCoord: coordinate in pixels 141 @type clipSizePix: float or list in format [widthPix, heightPix] 142 @param clipSizePix: if float, size of square clipped section in pixels; if list, 143 size of clipped section in pixels in x, y axes of output image respectively 144 @rtype: numpy array 145 @return: clipped image section 146 147 """ 148 149 imHeight=imageData.shape[0] 150 imWidth=imageData.shape[1] 151 152 if type(clipSizePix) == float or type(clipSizePix) == int: 153 xHalfClipSizePix=int(round(clipSizePix/2.0)) 154 yHalfClipSizePix=xHalfClipSizePix 155 elif type(clipSizePix) == list or type(clipSizePix) == tuple: 156 xHalfClipSizePix=int(round(clipSizePix[0]/2.0)) 157 yHalfClipSizePix=int(round(clipSizePix[1]/2.0)) 158 else: 159 raise Exception, "did not understand clipSizePix: should be float, or [widthPix, heightPix]" 160 161 cTopLeft=[XCoord+xHalfClipSizePix, YCoord+yHalfClipSizePix] 162 cBottomRight=[XCoord-xHalfClipSizePix, YCoord-yHalfClipSizePix] 163 164 X=[int(round(cTopLeft[0])),int(round(cBottomRight[0]))] 165 Y=[int(round(cTopLeft[1])),int(round(cBottomRight[1]))] 166 167 X.sort() 168 Y.sort() 169 170 if X[0] < 0: 171 X[0]=0 172 if X[1] > imWidth: 173 X[1]=imWidth 174 if Y[0] < 0: 175 Y[0]=0 176 if Y[1] > imHeight: 177 Y[1]=imHeight 178 179 return imageData[Y[0]:Y[1],X[0]:X[1]]
180 181 #---------------------------------------------------------------------------------------------------
182 -def clipRotatedImageSectionWCS(imageData, imageWCS, RADeg, decDeg, clipSizeDeg, returnWCS = True):
183 """Clips a square or rectangular section from an image array at the given celestial coordinates. 184 The resulting clip is rotated and/or flipped such that North is at the top, and East appears at 185 the left. An updated WCS for the clipped section is also returned. Note that the alignment 186 of the rotated WCS is currently not perfect - however, it is probably good enough in most 187 cases for use with L{ImagePlot} for plotting purposes. 188 189 Note that the clip size is specified in degrees on the sky. For projections that have varying 190 real pixel scale across the map (e.g. CEA), use L{clipUsingRADecCoords} instead. 191 192 @type imageData: numpy array 193 @param imageData: image data array 194 @type imageWCS: astWCS.WCS 195 @param imageWCS: astWCS.WCS object 196 @type RADeg: float 197 @param RADeg: coordinate in decimal degrees 198 @type decDeg: float 199 @param decDeg: coordinate in decimal degrees 200 @type clipSizeDeg: float 201 @param clipSizeDeg: if float, size of square clipped section in decimal degrees; if list, 202 size of clipped section in degrees in RA, dec. axes of output rotated image respectively 203 @type returnWCS: bool 204 @param returnWCS: if True, return an updated WCS for the clipped section 205 @rtype: dictionary 206 @return: clipped image section (numpy array), updated astWCS WCS object for 207 clipped image section, in format {'data', 'wcs'}. 208 209 @note: Returns 'None' if the requested position is not found within the image. If the image 210 WCS does not have keywords of the form CD1_1 etc., the output WCS will not be rotated. 211 212 """ 213 214 halfImageSize=imageWCS.getHalfSizeDeg() 215 imageCentre=imageWCS.getCentreWCSCoords() 216 imScale=imageWCS.getPixelSizeDeg() 217 218 if type(clipSizeDeg) == float: 219 xHalfClipSizeDeg=clipSizeDeg/2.0 220 yHalfClipSizeDeg=xHalfClipSizeDeg 221 elif type(clipSizeDeg) == list or type(clipSizeDeg) == tuple: 222 xHalfClipSizeDeg=clipSizeDeg[0]/2.0 223 yHalfClipSizeDeg=clipSizeDeg[1]/2.0 224 else: 225 raise Exception, "did not understand clipSizeDeg: should be float, or [widthDeg, heightDeg]" 226 227 diagonalHalfSizeDeg=math.sqrt((xHalfClipSizeDeg*xHalfClipSizeDeg) \ 228 +(yHalfClipSizeDeg*yHalfClipSizeDeg)) 229 230 diagonalHalfSizePix=diagonalHalfSizeDeg/imScale 231 232 if RADeg>imageCentre[0]-halfImageSize[0] and RADeg<imageCentre[0]+halfImageSize[0] \ 233 and decDeg>imageCentre[1]-halfImageSize[1] and decDeg<imageCentre[1]+halfImageSize[1]: 234 235 imageDiagonalClip=clipImageSectionWCS(imageData, imageWCS, RADeg, 236 decDeg, diagonalHalfSizeDeg*2.0) 237 diagonalClip=imageDiagonalClip['data'] 238 diagonalWCS=imageDiagonalClip['wcs'] 239 240 rotDeg=diagonalWCS.getRotationDeg() 241 imageRotated=ndimage.rotate(diagonalClip, rotDeg) 242 if diagonalWCS.isFlipped() == 1: 243 imageRotated=pylab.fliplr(imageRotated) 244 245 # Handle WCS rotation 246 rotatedWCS=diagonalWCS.copy() 247 rotRadians=math.radians(rotDeg) 248 249 if returnWCS == True: 250 try: 251 252 CD11=rotatedWCS.header['CD1_1'] 253 CD21=rotatedWCS.header['CD2_1'] 254 CD12=rotatedWCS.header['CD1_2'] 255 CD22=rotatedWCS.header['CD2_2'] 256 if rotatedWCS.isFlipped() == 1: 257 CD11=CD11*-1 258 CD12=CD12*-1 259 CDMatrix=numpy.array([[CD11, CD12], [CD21, CD22]], dtype=numpy.float64) 260 261 rotRadians=rotRadians 262 rot11=math.cos(rotRadians) 263 rot12=math.sin(rotRadians) 264 rot21=-math.sin(rotRadians) 265 rot22=math.cos(rotRadians) 266 rotMatrix=numpy.array([[rot11, rot12], [rot21, rot22]], dtype=numpy.float64) 267 newCDMatrix=numpy.dot(rotMatrix, CDMatrix) 268 269 P1=diagonalWCS.header['CRPIX1'] 270 P2=diagonalWCS.header['CRPIX2'] 271 V1=diagonalWCS.header['CRVAL1'] 272 V2=diagonalWCS.header['CRVAL2'] 273 274 PMatrix=numpy.zeros((2,), dtype = numpy.float64) 275 PMatrix[0]=P1 276 PMatrix[1]=P2 277 278 # BELOW IS HOW TO WORK OUT THE NEW REF PIXEL 279 CMatrix=numpy.array([imageRotated.shape[1]/2.0, imageRotated.shape[0]/2.0]) 280 centreCoords=diagonalWCS.getCentreWCSCoords() 281 alphaRad=math.radians(centreCoords[0]) 282 deltaRad=math.radians(centreCoords[1]) 283 thetaRad=math.asin(math.sin(deltaRad)*math.sin(math.radians(V2)) + \ 284 math.cos(deltaRad)*math.cos(math.radians(V2))*math.cos(alphaRad-math.radians(V1))) 285 phiRad=math.atan2(-math.cos(deltaRad)*math.sin(alphaRad-math.radians(V1)), \ 286 math.sin(deltaRad)*math.cos(math.radians(V2)) - \ 287 math.cos(deltaRad)*math.sin(math.radians(V2))*math.cos(alphaRad-math.radians(V1))) + \ 288 math.pi 289 RTheta=(180.0/math.pi)*(1.0/math.tan(thetaRad)) 290 291 xy=numpy.zeros((2,), dtype=numpy.float64) 292 xy[0]=RTheta*math.sin(phiRad) 293 xy[1]=-RTheta*math.cos(phiRad) 294 newPMatrix=CMatrix - numpy.dot(numpy.linalg.inv(newCDMatrix), xy) 295 296 # But there's a small offset to CRPIX due to the rotatedImage being rounded to an integer 297 # number of pixels (not sure this helps much) 298 #d=numpy.dot(rotMatrix, [diagonalClip.shape[1], diagonalClip.shape[0]]) 299 #offset=abs(d)-numpy.array(imageRotated.shape) 300 301 rotatedWCS.header.update('NAXIS1', imageRotated.shape[1]) 302 rotatedWCS.header.update('NAXIS2', imageRotated.shape[0]) 303 rotatedWCS.header.update('CRPIX1', newPMatrix[0]) 304 rotatedWCS.header.update('CRPIX2', newPMatrix[1]) 305 rotatedWCS.header.update('CRVAL1', V1) 306 rotatedWCS.header.update('CRVAL2', V2) 307 rotatedWCS.header.update('CD1_1', newCDMatrix[0][0]) 308 rotatedWCS.header.update('CD2_1', newCDMatrix[1][0]) 309 rotatedWCS.header.update('CD1_2', newCDMatrix[0][1]) 310 rotatedWCS.header.update('CD2_2', newCDMatrix[1][1]) 311 rotatedWCS.updateFromHeader() 312 313 except KeyError: 314 315 if REPORT_ERRORS == True: 316 print "WARNING: astImages.clipRotatedImageSectionWCS() : no CDi_j keywords found - not rotating WCS." 317 318 imageRotated=diagonalClip 319 rotatedWCS=diagonalWCS 320 321 imageRotatedClip=clipImageSectionWCS(imageRotated, rotatedWCS, RADeg, decDeg, clipSizeDeg) 322 323 if returnWCS == True: 324 return {'data': imageRotatedClip['data'], 'wcs': imageRotatedClip['wcs']} 325 else: 326 return {'data': imageRotatedClip['data'], 'wcs': None} 327 328 else: 329 330 if REPORT_ERRORS==True: 331 print """ERROR: astImages.clipRotatedImageSectionWCS() : 332 RADeg, decDeg are not within imageData.""" 333 334 return None
335 336 #---------------------------------------------------------------------------------------------------
337 -def clipUsingRADecCoords(imageData, imageWCS, RAMin, RAMax, decMin, decMax, returnWCS = True):
338 """Clips a section from an image array at the pixel coordinates corresponding to the given 339 celestial coordinates. 340 341 @type imageData: numpy array 342 @param imageData: image data array 343 @type imageWCS: astWCS.WCS 344 @param imageWCS: astWCS.WCS object 345 @type RAMin: float 346 @param RAMin: minimum RA coordinate in decimal degrees 347 @type RAMax: float 348 @param RAMax: maximum RA coordinate in decimal degrees 349 @type decMin: float 350 @param decMin: minimum dec coordinate in decimal degrees 351 @type decMax: float 352 @param decMax: maximum dec coordinate in decimal degrees 353 @type returnWCS: bool 354 @param returnWCS: if True, return an updated WCS for the clipped section 355 @rtype: dictionary 356 @return: clipped image section (numpy array), updated astWCS WCS object for 357 clipped image section, in format {'data', 'wcs'}. 358 359 @note: Returns 'None' if the requested position is not found within the image. If the image 360 WCS does not have keywords of the form CD1_1 etc., the output WCS will not be rotated. 361 362 """ 363 364 imHeight=imageData.shape[0] 365 imWidth=imageData.shape[1] 366 367 xMin, yMin=imageWCS.wcs2pix(RAMin, decMin) 368 xMax, yMax=imageWCS.wcs2pix(RAMax, decMax) 369 xMin=int(round(xMin)) 370 xMax=int(round(xMax)) 371 yMin=int(round(yMin)) 372 yMax=int(round(yMax)) 373 X=[xMin, xMax] 374 X.sort() 375 Y=[yMin, yMax] 376 Y.sort() 377 378 if X[0] < 0: 379 X[0]=0 380 if X[1] > imWidth: 381 X[1]=imWidth 382 if Y[0] < 0: 383 Y[0]=0 384 if Y[1] > imHeight: 385 Y[1]=imHeight 386 387 clippedData=imageData[Y[0]:Y[1],X[0]:X[1]] 388 389 # Update WCS 390 if returnWCS == True: 391 try: 392 oldCRPIX1=imageWCS.header['CRPIX1'] 393 oldCRPIX2=imageWCS.header['CRPIX2'] 394 clippedWCS=imageWCS.copy() 395 clippedWCS.header.update('NAXIS1', clippedData.shape[1]) 396 clippedWCS.header.update('NAXIS2', clippedData.shape[0]) 397 clippedWCS.header.update('CRPIX1', oldCRPIX1-X[0]) 398 clippedWCS.header.update('CRPIX2', oldCRPIX2-Y[0]) 399 clippedWCS.updateFromHeader() 400 401 except KeyError: 402 403 if REPORT_ERRORS == True: 404 405 print "WARNING: astImages.clipUsingRADecCoords() : no CRPIX1, CRPIX2 keywords found - not updating clipped image WCS." 406 407 clippedData=imageData[Y[0]:Y[1],X[0]:X[1]] 408 clippedWCS=imageWCS.copy() 409 else: 410 clippedWCS=None 411 412 return {'data': clippedData, 'wcs': clippedWCS}
413 414 #---------------------------------------------------------------------------------------------------
415 -def scaleImage(imageData, imageWCS, scaleFactor):
416 """Scales image array and WCS by the given scale factor. 417 418 @type imageData: numpy array 419 @param imageData: image data array 420 @type imageWCS: astWCS.WCS 421 @param imageWCS: astWCS.WCS object 422 @type scaleFactor: float or list or tuple 423 @param scaleFactor: factor to resize image by - if tuple or list, in format 424 [x scale factor, y scale factor] 425 @rtype: dictionary 426 @return: image data (numpy array), updated astWCS WCS object for image, in format {'data', 'wcs'}. 427 428 """ 429 430 if type(scaleFactor) == int or type(scaleFactor) == float: 431 scaleFactor=[scaleFactor, scaleFactor] 432 scaledData=ndimage.zoom(imageData, scaleFactor) 433 434 # Take care of offset due to rounding in scaling image to integer pixel dimensions 435 properDimensions=numpy.array(imageData.shape)*scaleFactor 436 offset=properDimensions-numpy.array(scaledData.shape) 437 438 # Rescale WCS 439 try: 440 oldCRPIX1=imageWCS.header['CRPIX1'] 441 oldCRPIX2=imageWCS.header['CRPIX2'] 442 CD11=imageWCS.header['CD1_1'] 443 CD21=imageWCS.header['CD2_1'] 444 CD12=imageWCS.header['CD1_2'] 445 CD22=imageWCS.header['CD2_2'] 446 447 CDMatrix=numpy.array([[CD11, CD12], [CD21, CD22]], dtype=numpy.float64) 448 scaleFactorMatrix=numpy.array([[1.0/scaleFactor[0], 0], [0, 1.0/scaleFactor[1]]]) 449 scaledCDMatrix=numpy.dot(scaleFactorMatrix, CDMatrix) 450 451 scaledWCS=imageWCS.copy() 452 scaledWCS.header.update('NAXIS1', scaledData.shape[1]) 453 scaledWCS.header.update('NAXIS2', scaledData.shape[0]) 454 scaledWCS.header.update('CRPIX1', oldCRPIX1*scaleFactor[0]+offset[1]) 455 scaledWCS.header.update('CRPIX2', oldCRPIX2*scaleFactor[1]+offset[0]) 456 scaledWCS.header.update('CD1_1', scaledCDMatrix[0][0]) 457 scaledWCS.header.update('CD2_1', scaledCDMatrix[1][0]) 458 scaledWCS.header.update('CD1_2', scaledCDMatrix[0][1]) 459 scaledWCS.header.update('CD2_2', scaledCDMatrix[1][1]) 460 scaledWCS.updateFromHeader() 461 462 except KeyError: 463 464 if REPORT_ERRORS == True: 465 466 print "WARNING: astImages.rescaleImage() : no CDij, keywords found - not updating WCS." 467 scaledWCS=imageWCS.copy() 468 469 return {'data': scaledData, 'wcs': scaledWCS}
470 471 #---------------------------------------------------------------------------------------------------
472 -def intensityCutImage(imageData, cutLevels):
473 """Creates a matplotlib.pylab plot of an image array with the specified cuts in intensity 474 applied. This routine is used by L{saveBitmap} and L{saveContourOverlayBitmap}, which both 475 produce output as .png, .jpg, etc. images. 476 477 @type imageData: numpy array 478 @param imageData: image data array 479 @type cutLevels: list 480 @param cutLevels: sets the image scaling - available options: 481 - pixel values: cutLevels=[low value, high value]. 482 - histogram equalisation: cutLevels=["histEq", number of bins ( e.g. 1024)] 483 - relative: cutLevels=["relative", cut per cent level (e.g. 99.5)] 484 - smart: cutLevels=["smart", cut per cent level (e.g. 99.5)] 485 ["smart", 99.5] seems to provide good scaling over a range of different images. 486 @rtype: dictionary 487 @return: image section (numpy.array), matplotlib image normalisation (matplotlib.colors.Normalize), in the format {'image', 'norm'}. 488 489 @note: If cutLevels[0] == "histEq", then only {'image'} is returned. 490 491 """ 492 493 oImWidth=imageData.shape[1] 494 oImHeight=imageData.shape[0] 495 496 # Optional histogram equalisation 497 if cutLevels[0]=="histEq": 498 499 imageData=histEq(imageData, cutLevels[1]) 500 anorm=pylab.normalize(imageData.min(), imageData.max()) 501 502 elif cutLevels[0]=="relative": 503 504 # this turns image data into 1D array then sorts 505 sorted=numpy.sort(numpy.ravel(imageData)) 506 maxValue=sorted.max() 507 minValue=sorted.min() 508 509 # want to discard the top and bottom specified 510 topCutIndex=len(sorted-1) \ 511 -int(math.floor(float((100.0-cutLevels[1])/100.0)*len(sorted-1))) 512 bottomCutIndex=int(math.ceil(float((100.0-cutLevels[1])/100.0)*len(sorted-1))) 513 topCut=sorted[topCutIndex] 514 bottomCut=sorted[bottomCutIndex] 515 anorm=pylab.normalize(bottomCut, topCut) 516 517 elif cutLevels[0]=="smart": 518 519 # this turns image data into 1Darray then sorts 520 sorted=numpy.sort(numpy.ravel(imageData)) 521 maxValue=sorted.max() 522 minValue=sorted.min() 523 numBins=10000 # 0.01 per cent accuracy 524 binWidth=(maxValue-minValue)/float(numBins) 525 histogram=ndimage.histogram(sorted, minValue, maxValue, numBins) 526 527 # Find the bin with the most pixels in it, set that as our minimum 528 # Then search through the bins until we get to a bin with more/or the same number of 529 # pixels in it than the previous one. 530 # We take that to be the maximum. 531 # This means that we avoid the traps of big, bright, saturated stars that cause 532 # problems for relative scaling 533 backgroundValue=histogram.max() 534 foundBackgroundBin=False 535 foundTopBin=False 536 lastBin=-10000 537 for i in range(len(histogram)): 538 539 if histogram[i]>=lastBin and foundBackgroundBin==True: 540 541 # Added a fudge here to stop us picking for top bin a bin within 542 # 10 percent of the background pixel value 543 if (minValue+(binWidth*i))>bottomBinValue*1.1: 544 topBinValue=minValue+(binWidth*i) 545 foundTopBin=True 546 break 547 548 if histogram[i]==backgroundValue and foundBackgroundBin==False: 549 bottomBinValue=minValue+(binWidth*i) 550 foundBackgroundBin=True 551 552 lastBin=histogram[i] 553 554 if foundTopBin==False: 555 topBinValue=maxValue 556 557 #Now we apply relative scaling to this 558 smartClipped=numpy.clip(sorted, bottomBinValue, topBinValue) 559 topCutIndex=len(smartClipped-1) \ 560 -int(math.floor(float((100.0-cutLevels[1])/100.0)*len(smartClipped-1))) 561 bottomCutIndex=int(math.ceil(float((100.0-cutLevels[1])/100.0)*len(smartClipped-1))) 562 topCut=smartClipped[topCutIndex] 563 bottomCut=smartClipped[bottomCutIndex] 564 anorm=pylab.normalize(bottomCut, topCut) 565 else: 566 567 # Normalise using given cut levels 568 anorm=pylab.normalize(cutLevels[0], cutLevels[1]) 569 570 if cutLevels[0]=="histEq": 571 return {'image': imageData.copy()} 572 else: 573 return {'image': imageData.copy(), 'norm': anorm}
574 575 #---------------------------------------------------------------------------------------------------
576 -def resampleToTanProjection(imageData, imageWCS, outputPixDimensions=[600, 600]):
577 """Resamples an image and WCS to a tangent plane projection. Purely for plotting purposes 578 (e.g., ensuring RA, dec. coordinate axes perpendicular). 579 580 @type imageData: numpy array 581 @param imageData: image data array 582 @type imageWCS: astWCS.WCS 583 @param imageWCS: astWCS.WCS object 584 @type outputPixDimensions: list 585 @param outputPixDimensions: [width, height] of output image in pixels 586 @rtype: dictionary 587 @return: image data (numpy array), updated astWCS WCS object for image, in format {'data', 'wcs'}. 588 589 """ 590 591 RADeg, decDeg=imageWCS.getCentreWCSCoords() 592 xPixelScale=imageWCS.getXPixelSizeDeg() 593 yPixelScale=imageWCS.getYPixelSizeDeg() 594 xSizeDeg, ySizeDeg=imageWCS.getFullSizeSkyDeg() 595 xSizePix=int(round(outputPixDimensions[0])) 596 ySizePix=int(round(outputPixDimensions[1])) 597 xRefPix=xSizePix/2.0 598 yRefPix=ySizePix/2.0 599 xOutPixScale=xSizeDeg/xSizePix 600 yOutPixScale=ySizeDeg/ySizePix 601 cardList=pyfits.CardList() 602 cardList.append(pyfits.Card('NAXIS', 2)) 603 cardList.append(pyfits.Card('NAXIS1', xSizePix)) 604 cardList.append(pyfits.Card('NAXIS2', ySizePix)) 605 cardList.append(pyfits.Card('CTYPE1', 'RA---TAN')) 606 cardList.append(pyfits.Card('CTYPE2', 'DEC--TAN')) 607 cardList.append(pyfits.Card('CRVAL1', RADeg)) 608 cardList.append(pyfits.Card('CRVAL2', decDeg)) 609 cardList.append(pyfits.Card('CRPIX1', xRefPix+1)) 610 cardList.append(pyfits.Card('CRPIX2', yRefPix+1)) 611 cardList.append(pyfits.Card('CDELT1', xOutPixScale)) 612 cardList.append(pyfits.Card('CDELT2', yOutPixScale)) 613 cardList.append(pyfits.Card('CUNIT1', 'DEG')) 614 cardList.append(pyfits.Card('CUNIT2', 'DEG')) 615 newHead=pyfits.Header(cards=cardList) 616 newWCS=astWCS.WCS(newHead, mode='pyfits') 617 newImage=numpy.zeros([ySizePix, xSizePix]) 618 619 tanImage=resampleToWCS(newImage, newWCS, imageData, imageWCS, highAccuracy=True, 620 onlyOverlapping=False) 621 622 return tanImage
623 624 #---------------------------------------------------------------------------------------------------
625 -def resampleToWCS(im1Data, im1WCS, im2Data, im2WCS, highAccuracy = False, onlyOverlapping = True):
626 """Resamples data corresponding to second image (with data im2Data, WCS im2WCS) onto the WCS 627 of the first image (im1Data, im1WCS). The output, resampled image is of the pixel same 628 dimensions of the first image. This routine is for assisting in plotting - performing 629 photometry on the output is not recommended. 630 631 Set highAccuracy == True to sample every corresponding pixel in each image; otherwise only 632 every nth pixel (where n is the ratio of the image scales) will be sampled, with values 633 in between being set using a linear interpolation (much faster). 634 635 Set onlyOverlapping == True to speed up resampling by only resampling the overlapping 636 area defined by both image WCSs. 637 638 @type im1Data: numpy array 639 @param im1Data: image data array for first image 640 @type im1WCS: astWCS.WCS 641 @param im1WCS: astWCS.WCS object corresponding to im1Data 642 @type im2Data: numpy array 643 @param im2Data: image data array for second image (to be resampled to match first image) 644 @type im2WCS: astWCS.WCS 645 @param im2WCS: astWCS.WCS object corresponding to im2Data 646 @type highAccuracy: bool 647 @param highAccuracy: if True, sample every corresponding pixel in each image; otherwise, sample 648 every nth pixel, where n = the ratio of the image scales. 649 @type onlyOverlapping: bool 650 @param onlyOverlapping: if True, only consider the overlapping area defined by both image WCSs 651 (speeds things up) 652 @rtype: dictionary 653 @return: numpy image data array and associated WCS in format {'data', 'wcs'} 654 655 """ 656 657 resampledData=numpy.zeros(im1Data.shape) 658 659 # Find overlap - speed things up 660 # But have a border so as not to require the overlap to be perfect 661 # There's also no point in oversampling image 1 if it's much higher res than image 2 662 xPixRatio=(im2WCS.getXPixelSizeDeg()/im1WCS.getXPixelSizeDeg())/2.0 663 yPixRatio=(im2WCS.getYPixelSizeDeg()/im1WCS.getYPixelSizeDeg())/2.0 664 xBorder=xPixRatio*10.0 665 yBorder=yPixRatio*10.0 666 if highAccuracy == False: 667 if xPixRatio > 1: 668 xPixStep=int(math.ceil(xPixRatio)) 669 else: 670 xPixStep=1 671 if yPixRatio > 1: 672 yPixStep=int(math.ceil(yPixRatio)) 673 else: 674 yPixStep=1 675 else: 676 xPixStep=1 677 yPixStep=1 678 679 if onlyOverlapping == True: 680 overlap=astWCS.findWCSOverlap(im1WCS, im2WCS) 681 xOverlap=[overlap['wcs1Pix'][0], overlap['wcs1Pix'][1]] 682 yOverlap=[overlap['wcs1Pix'][2], overlap['wcs1Pix'][3]] 683 xOverlap.sort() 684 yOverlap.sort() 685 xMin=int(math.floor(xOverlap[0]-xBorder)) 686 xMax=int(math.ceil(xOverlap[1]+xBorder)) 687 yMin=int(math.floor(yOverlap[0]-yBorder)) 688 yMax=int(math.ceil(yOverlap[1]+yBorder)) 689 xRemainder=(xMax-xMin) % xPixStep 690 yRemainder=(yMax-yMin) % yPixStep 691 if xRemainder != 0: 692 xMax=xMax+xRemainder 693 if yRemainder != 0: 694 yMax=yMax+yRemainder 695 # Check that we're still within the image boundaries, to be on the safe side 696 if xMin < 0: 697 xMin=0 698 if xMax > im1Data.shape[1]: 699 xMax=im1Data.shape[1] 700 if yMin < 0: 701 yMin=0 702 if yMax > im1Data.shape[0]: 703 yMax=im1Data.shape[0] 704 else: 705 xMin=0 706 xMax=im1Data.shape[1] 707 yMin=0 708 yMax=im1Data.shape[0] 709 710 for x in range(xMin, xMax, xPixStep): 711 for y in range(yMin, yMax, yPixStep): 712 RA, dec=im1WCS.pix2wcs(x, y) 713 x2, y2=im2WCS.wcs2pix(RA, dec) 714 x2=int(round(x2)) 715 y2=int(round(y2)) 716 if x2 >= 0 and x2 < im2Data.shape[1] and y2 >= 0 and y2 < im2Data.shape[0]: 717 resampledData[y][x]=im2Data[y2][x2] 718 719 # linear interpolation 720 if highAccuracy == False: 721 for row in range(resampledData.shape[0]): 722 vals=resampledData[row, numpy.arange(xMin, xMax, xPixStep)] 723 index2data=interpolate.interp1d(numpy.arange(0, vals.shape[0], 1), vals) 724 interpedVals=index2data(numpy.arange(0, vals.shape[0]-1, 1.0/xPixStep)) 725 resampledData[row, xMin:xMin+interpedVals.shape[0]]=interpedVals 726 for col in range(resampledData.shape[1]): 727 vals=resampledData[numpy.arange(yMin, yMax, yPixStep), col] 728 index2data=interpolate.interp1d(numpy.arange(0, vals.shape[0], 1), vals) 729 interpedVals=index2data(numpy.arange(0, vals.shape[0]-1, 1.0/yPixStep)) 730 resampledData[yMin:yMin+interpedVals.shape[0], col]=interpedVals 731 732 # Note: should really just copy im1WCS keywords into im2WCS and return that 733 # Only a problem if we're using this for anything other than plotting 734 return {'data': resampledData, 'wcs': im1WCS.copy()}
735 736 #---------------------------------------------------------------------------------------------------
737 -def generateContourOverlay(backgroundImageData, backgroundImageWCS, contourImageData, contourImageWCS, \ 738 contourLevels, contourSmoothFactor = 0, highAccuracy = False):
739 """Rescales an image array to be used as a contour overlay to have the same dimensions as the 740 background image, and generates a set of contour levels. The image array from which the contours 741 are to be generated will be resampled to the same dimensions as the background image data, and 742 can be optionally smoothed using a Gaussian filter. The sigma of the Gaussian filter 743 (contourSmoothFactor) is specified in arcsec. 744 745 @type backgroundImageData: numpy array 746 @param backgroundImageData: background image data array 747 @type backgroundImageWCS: astWCS.WCS 748 @param backgroundImageWCS: astWCS.WCS object of the background image data array 749 @type contourImageData: numpy array 750 @param contourImageData: image data array from which contours are to be generated 751 @type contourImageWCS: astWCS.WCS 752 @param contourImageWCS: astWCS.WCS object corresponding to contourImageData 753 @type contourLevels: list 754 @param contourLevels: sets the contour levels - available options: 755 - values: contourLevels=[list of values specifying each level] 756 - linear spacing: contourLevels=['linear', min level value, max level value, number 757 of levels] - can use "min", "max" to automatically set min, max levels from image data 758 - log spacing: contourLevels=['log', min level value, max level value, number of 759 levels] - can use "min", "max" to automatically set min, max levels from image data 760 @type contourSmoothFactor: float 761 @param contourSmoothFactor: standard deviation (in arcsec) of Gaussian filter for 762 pre-smoothing of contour image data (set to 0 for no smoothing) 763 @type highAccuracy: bool 764 @param highAccuracy: if True, sample every corresponding pixel in each image; otherwise, sample 765 every nth pixel, where n = the ratio of the image scales. 766 767 """ 768 769 # For compromise between speed and accuracy, scale a copy of the background 770 # image down to a scale that is one pixel = 1/5 of a pixel in the contour image 771 # But only do this if it has CDij keywords as we know how to scale those 772 if backgroundImageWCS.header.has_key("CD1_1") == True: 773 xScaleFactor=backgroundImageWCS.getXPixelSizeDeg()/(contourImageWCS.getXPixelSizeDeg()/5.0) 774 yScaleFactor=backgroundImageWCS.getYPixelSizeDeg()/(contourImageWCS.getYPixelSizeDeg()/5.0) 775 scaledBackground=scaleImage(backgroundImageData, backgroundImageWCS, (xScaleFactor, yScaleFactor)) 776 scaled=resampleToWCS(scaledBackground['data'], scaledBackground['wcs'], 777 contourImageData, contourImageWCS, highAccuracy = highAccuracy) 778 scaledContourData=scaled['data'] 779 scaledContourWCS=scaled['wcs'] 780 scaledBackground=True 781 else: 782 scaled=resampleToWCS(backgroundImageData, backgroundImageWCS, 783 contourImageData, contourImageWCS, highAccuracy = highAccuracy) 784 scaledContourData=scaled['data'] 785 scaledContourWCS=scaled['wcs'] 786 scaledBackground=False 787 788 if contourSmoothFactor > 0: 789 sigmaPix=(contourSmoothFactor/3600.0)/scaledContourWCS.getPixelSizeDeg() 790 scaledContourData=ndimage.gaussian_filter(scaledContourData, sigmaPix) 791 792 # Various ways of setting the contour levels 793 # If just a list is passed in, use those instead 794 if contourLevels[0] == "linear": 795 if contourLevels[1] == "min": 796 xMin=contourImageData.flatten().min() 797 else: 798 xMin=float(contourLevels[1]) 799 if contourLevels[2] == "max": 800 xMax=contourImageData.flatten().max() 801 else: 802 xMax=float(contourLevels[2]) 803 nLevels=contourLevels[3] 804 xStep=(xMax-xMin)/(nLevels-1) 805 cLevels=[] 806 for j in range(nLevels+1): 807 level=xMin+j*xStep 808 cLevels.append(level) 809 810 elif contourLevels[0] == "log": 811 if contourLevels[1] == "min": 812 xMin=contourImageData.flatten().min() 813 else: 814 xMin=float(contourLevels[1]) 815 if contourLevels[2] == "max": 816 xMax=contourImageData.flatten().max() 817 else: 818 xMax=float(contourLevels[2]) 819 if xMin <= 0.0: 820 raise Exception, "minimum contour level set to <= 0 and log scaling chosen." 821 xLogMin=math.log10(xMin) 822 xLogMax=math.log10(xMax) 823 nLevels=contourLevels[3] 824 xLogStep=(xLogMax-xLogMin)/(nLevels-1) 825 cLevels=[] 826 prevLevel=0 827 for j in range(nLevels+1): 828 level=math.pow(10, xLogMin+j*xLogStep) 829 cLevels.append(level) 830 831 else: 832 cLevels=contourLevels 833 834 # Now blow the contour image data back up to the size of the original image 835 if scaledBackground == True: 836 scaledBack=scaleImage(scaledContourData, scaledContourWCS, (1.0/xScaleFactor, 1.0/yScaleFactor))['data'] 837 else: 838 scaledBack=scaledContourData 839 840 return {'scaledImage': scaledBack, 'contourLevels': cLevels}
841 842 #---------------------------------------------------------------------------------------------------
843 -def saveBitmap(outputFileName, imageData, cutLevels, size, colorMapName):
844 """Makes a bitmap image from an image array; the image format is specified by the 845 filename extension. (e.g. ".jpg" =JPEG, ".png"=PNG). 846 847 @type outputFileName: string 848 @param outputFileName: filename of output bitmap image 849 @type imageData: numpy array 850 @param imageData: image data array 851 @type cutLevels: list 852 @param cutLevels: sets the image scaling - available options: 853 - pixel values: cutLevels=[low value, high value]. 854 - histogram equalisation: cutLevels=["histEq", number of bins ( e.g. 1024)] 855 - relative: cutLevels=["relative", cut per cent level (e.g. 99.5)] 856 - smart: cutLevels=["smart", cut per cent level (e.g. 99.5)] 857 ["smart", 99.5] seems to provide good scaling over a range of different images. 858 @type size: int 859 @param size: size of output image in pixels 860 @type colorMapName: string 861 @param colorMapName: name of a standard matplotlib colormap, e.g. "hot", "cool", "gray" 862 etc. (do "help(pylab.colormaps)" in the Python interpreter to see available options) 863 864 """ 865 866 cut=intensityCutImage(imageData, cutLevels) 867 868 # Make plot 869 aspectR=float(cut['image'].shape[0])/float(cut['image'].shape[1]) 870 pylab.figure(figsize=(10,10*aspectR)) 871 pylab.axes([0,0,1,1]) 872 873 try: 874 colorMap=pylab.cm.get_cmap(colorMapName) 875 except AssertionError: 876 raise Exception, colorMapName+" is not a defined matplotlib colormap." 877 878 if cutLevels[0]=="histEq": 879 pylab.imshow(cut['image'], interpolation="bilinear", origin='lower', cmap=colorMap) 880 881 else: 882 pylab.imshow(cut['image'], interpolation="bilinear", norm=cut['norm'], origin='lower', 883 cmap=colorMap) 884 885 pylab.axis("off") 886 887 pylab.savefig("out_astImages.png") 888 pylab.close("all") 889 890 im=Image.open("out_astImages.png") 891 im.thumbnail((int(size),int(size))) 892 im.save(outputFileName) 893 894 os.remove("out_astImages.png")
895 896 #---------------------------------------------------------------------------------------------------
897 -def saveContourOverlayBitmap(outputFileName, backgroundImageData, backgroundImageWCS, cutLevels, \ 898 size, colorMapName, contourImageData, contourImageWCS, \ 899 contourSmoothFactor, contourLevels, contourColor, contourWidth):
900 """Makes a bitmap image from an image array, with a set of contours generated from a 901 second image array overlaid. The image format is specified by the file extension 902 (e.g. ".jpg"=JPEG, ".png"=PNG). The image array from which the contours are to be generated 903 can optionally be pre-smoothed using a Gaussian filter. 904 905 @type outputFileName: string 906 @param outputFileName: filename of output bitmap image 907 @type backgroundImageData: numpy array 908 @param backgroundImageData: background image data array 909 @type backgroundImageWCS: astWCS.WCS 910 @param backgroundImageWCS: astWCS.WCS object of the background image data array 911 @type cutLevels: list 912 @param cutLevels: sets the image scaling - available options: 913 - pixel values: cutLevels=[low value, high value]. 914 - histogram equalisation: cutLevels=["histEq", number of bins ( e.g. 1024)] 915 - relative: cutLevels=["relative", cut per cent level (e.g. 99.5)] 916 - smart: cutLevels=["smart", cut per cent level (e.g. 99.5)] 917 ["smart", 99.5] seems to provide good scaling over a range of different images. 918 @type size: int 919 @param size: size of output image in pixels 920 @type colorMapName: string 921 @param colorMapName: name of a standard matplotlib colormap, e.g. "hot", "cool", "gray" 922 etc. (do "help(pylab.colormaps)" in the Python interpreter to see available options) 923 @type contourImageData: numpy array 924 @param contourImageData: image data array from which contours are to be generated 925 @type contourImageWCS: astWCS.WCS 926 @param contourImageWCS: astWCS.WCS object corresponding to contourImageData 927 @type contourSmoothFactor: float 928 @param contourSmoothFactor: standard deviation (in pixels) of Gaussian filter for 929 pre-smoothing of contour image data (set to 0 for no smoothing) 930 @type contourLevels: list 931 @param contourLevels: sets the contour levels - available options: 932 - values: contourLevels=[list of values specifying each level] 933 - linear spacing: contourLevels=['linear', min level value, max level value, number 934 of levels] - can use "min", "max" to automatically set min, max levels from image data 935 - log spacing: contourLevels=['log', min level value, max level value, number of 936 levels] - can use "min", "max" to automatically set min, max levels from image data 937 @type contourColor: string 938 @param contourColor: color of the overlaid contours, specified by the name of a standard 939 matplotlib color, e.g., "black", "white", "cyan" 940 etc. (do "help(pylab.colors)" in the Python interpreter to see available options) 941 @type contourWidth: int 942 @param contourWidth: width of the overlaid contours 943 944 """ 945 946 cut=intensityCutImage(backgroundImageData, cutLevels) 947 948 # Make plot of just the background image 949 aspectR=float(cut['image'].shape[0])/float(cut['image'].shape[1]) 950 pylab.figure(figsize=(10,10*aspectR)) 951 pylab.axes([0,0,1,1]) 952 953 try: 954 colorMap=pylab.cm.get_cmap(colorMapName) 955 except AssertionError: 956 raise Exception, colorMapName+" is not a defined matplotlib colormap." 957 958 if cutLevels[0]=="histEq": 959 pylab.imshow(cut['image'], interpolation="bilinear", origin='lower', cmap=colorMap) 960 961 else: 962 pylab.imshow(cut['image'], interpolation="bilinear", norm=cut['norm'], origin='lower', 963 cmap=colorMap) 964 965 pylab.axis("off") 966 967 # Add the contours 968 contourData=generateContourOverlay(backgroundImageData, backgroundImageWCS, contourImageData, \ 969 contourImageWCS, contourLevels, contourSmoothFactor) 970 971 pylab.contour(contourData['scaledImage'], contourData['contourLevels'], colors=contourColor, 972 linewidths=contourWidth) 973 974 pylab.savefig("out_astImages.png") 975 pylab.close("all") 976 977 im=Image.open("out_astImages.png") 978 im.thumbnail((int(size),int(size))) 979 im.save(outputFileName) 980 981 os.remove("out_astImages.png")
982 983 #---------------------------------------------------------------------------------------------------
984 -def saveFITS(outputFileName, imageData, imageWCS = None):
985 """Writes an image array to a new .fits file. 986 987 @type outputFileName: string 988 @param outputFileName: filename of output FITS image 989 @type imageData: numpy array 990 @param imageData: image data array 991 @type imageWCS: astWCS.WCS object 992 @param imageWCS: image WCS object 993 994 @note: If imageWCS=None, the FITS image will be written with a rudimentary header containing 995 no meta data. 996 997 """ 998 999 if os.path.exists(outputFileName): 1000 os.remove(outputFileName) 1001 1002 newImg=pyfits.HDUList() 1003 1004 if imageWCS!=None: 1005 hdu=pyfits.PrimaryHDU(None, imageWCS.header) 1006 else: 1007 hdu=pyfits.PrimaryHDU(None, None) 1008 1009 hdu.data=imageData 1010 newImg.append(hdu) 1011 newImg.writeto(outputFileName) 1012 newImg.close()
1013 1014 #---------------------------------------------------------------------------------------------------
1015 -def histEq(inputArray, numBins):
1016 """Performs histogram equalisation of the input numpy array. 1017 1018 @type inputArray: numpy array 1019 @param inputArray: image data array 1020 @type numBins: int 1021 @param numBins: number of bins in which to perform the operation (e.g. 1024) 1022 @rtype: numpy array 1023 @return: image data array 1024 1025 """ 1026 1027 imageData=inputArray 1028 1029 # histogram equalisation: we want an equal number of pixels in each intensity range 1030 sortedDataIntensities=numpy.sort(numpy.ravel(imageData)) 1031 median=numpy.median(sortedDataIntensities) 1032 1033 # Make cumulative histogram of data values, simple min-max used to set bin sizes and range 1034 dataCumHist=numpy.zeros(numBins) 1035 minIntensity=sortedDataIntensities.min() 1036 maxIntensity=sortedDataIntensities.max() 1037 histRange=maxIntensity-minIntensity 1038 binWidth=histRange/float(numBins-1) 1039 for i in range(len(sortedDataIntensities)): 1040 binNumber=int(math.ceil((sortedDataIntensities[i]-minIntensity)/binWidth)) 1041 addArray=numpy.zeros(numBins) 1042 onesArray=numpy.ones(numBins-binNumber) 1043 onesRange=range(binNumber, numBins) 1044 numpy.put(addArray, onesRange, onesArray) 1045 dataCumHist=dataCumHist+addArray 1046 1047 # Make ideal cumulative histogram 1048 idealValue=dataCumHist.max()/float(numBins) 1049 idealCumHist=numpy.arange(idealValue, dataCumHist.max()+idealValue, idealValue) 1050 1051 # Map the data to the ideal 1052 for y in range(imageData.shape[0]): 1053 for x in range(imageData.shape[1]): 1054 # Get index corresponding to dataIntensity 1055 intensityBin=int(math.ceil((imageData[y][x]-minIntensity)/binWidth)) 1056 1057 # Guard against rounding errors (happens rarely I think) 1058 if intensityBin<0: 1059 intensityBin=0 1060 if intensityBin>len(dataCumHist)-1: 1061 intensityBin=len(dataCumHist)-1 1062 1063 # Get the cumulative frequency corresponding intensity level in the data 1064 dataCumFreq=dataCumHist[intensityBin] 1065 1066 # Get the index of the corresponding ideal cumulative frequency 1067 idealBin=numpy.searchsorted(idealCumHist, dataCumFreq) 1068 idealIntensity=(idealBin*binWidth)+minIntensity 1069 imageData[y][x]=idealIntensity 1070 1071 return imageData
1072 1073 #---------------------------------------------------------------------------------------------------
1074 -def normalise(inputArray, clipMinMax):
1075 """Clips the inputArray in intensity and normalises the array such that minimum and maximum 1076 values are 0, 1. Clip in intensity is specified by clipMinMax, a list in the format 1077 [clipMin, clipMax] 1078 1079 Used for normalising image arrays so that they can be turned into RGB arrays that matplotlib 1080 can plot (see L{astPlots.ImagePlot}). 1081 1082 @type inputArray: numpy array 1083 @param inputArray: image data array 1084 @type clipMinMax: list 1085 @param clipMinMax: [minimum value of clipped array, maximum value of clipped array] 1086 @rtype: numpy array 1087 @return: normalised array with minimum value 0, maximum value 1 1088 1089 """ 1090 clipped=inputArray.clip(clipMinMax[0], clipMinMax[1]) 1091 slope=1.0/(clipMinMax[1]-clipMinMax[0]) 1092 intercept=-clipMinMax[0]*slope 1093 clipped=clipped*slope+intercept 1094 1095 return clipped
1096 1097 #--------------------------------------------------------------------------------------------------- 1098