Package pyvision :: Package types :: Module img
[hide private]
[frames] | no frames]

Source Code for Module pyvision.types.img

   1  # PyVision License 
   2  # 
   3  # Copyright (c) 2006-2009 David S. Bolme 
   4  # All rights reserved. 
   5  # 
   6  # Redistribution and use in source and binary forms, with or without 
   7  # modification, are permitted provided that the following conditions 
   8  # are met: 
   9  #  
  10  # 1. Redistributions of source code must retain the above copyright 
  11  # notice, this list of conditions and the following disclaimer. 
  12  #  
  13  # 2. Redistributions in binary form must reproduce the above copyright 
  14  # notice, this list of conditions and the following disclaimer in the 
  15  # documentation and/or other materials provided with the distribution. 
  16  #  
  17  # 3. Neither name of copyright holders nor the names of its contributors 
  18  # may be used to endorse or promote products derived from this software 
  19  # without specific prior written permission. 
  20  #  
  21  #  
  22  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
  23  # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
  24  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
  25  # A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR 
  26  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  27  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
  28  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
  29  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
  30  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
  31  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
  32  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
  33   
  34  ''' 
  35   
  36  ''' 
  37  __author__ = "$Author$" 
  38  __version__ = "$Revision$" 
  39   
  40  # PIL Imports 
  41  import PIL.ImageDraw 
  42  import PIL.Image 
  43  from PIL.Image import BICUBIC, ANTIALIAS 
  44  import PIL.ImageFont as ImageFont 
  45   
  46  # Imaging imports 
  47  import numpy 
  48  import numpy as np 
  49  import cv 
  50  import cv2 
  51  import pyvision 
  52  import pyvision as pv 
  53   
  54  import cStringIO 
  55  import exif 
  56  import os 
  57   
  58  # iPython support for ipython notebook 
  59  try: 
  60      import pylab 
  61      import IPython 
  62  except: 
  63      pass # do nothing 
  64   
  65   
  66  TYPE_MATRIX_2D  = "TYPE_MATRIX2D"  
  67  '''Image was created using a 2D "gray-scale" numpy array''' 
  68   
  69  TYPE_MATRIX_RGB = "TYPE_MATRIX_RGB"  
  70  '''Image was created using a 3D "color" numpy array''' 
  71   
  72  TYPE_PIL        = "TYPE_PIL"  
  73  '''Image was created using a PIL image instance''' 
  74   
  75  TYPE_OPENCV     = "TYPE_OPENCV" 
  76  '''Image was created using a OpenCV image instance''' 
  77   
  78  TYPE_OPENCV2     = "TYPE_OPENCV2" 
  79  '''Image was created using a OpenCV image instance''' 
  80   
  81  TYPE_OPENCV2BW     = "TYPE_OPENCV2BW" 
  82  '''Image was created using a OpenCV image instance''' 
  83   
  84  LUMA = [0.299, 0.587, 0.114, 1.0] 
  85  '''Values used when converting color to gray-scale.''' 
  86   
87 -class Image:
88 ''' 89 The primary purpose of the image class is to provide a structure that can 90 transform an image back and fourth for different python libraries such as 91 U{PIL<http://www.pythonware.com/products/pil>}, 92 U{OpenCV <http://sourceforge.net/projects/opencvlibrary>}, and 93 U{Scipy<http://www.scipy.org">} Images. This class also 94 allows some simple operations on the image such as annotation. 95 96 B{Note:} When working with images in matrix format, they are transposed such 97 that x = col and y = row. You can therefore still work with coords 98 such that im[x,y] = mat[x,y]. # 99 100 Images have the following attributes: 101 - width = width of the image 102 - height = height of the image 103 - size = (width,height) 104 - channels = number of channels: 1(gray), 3(RGB) 105 - depth = bitdepth: 8(uchar), 32(float), 64(double) 106 ''' 107 108 109 #------------------------------------------------------------------------
110 - def __init__(self,data,bw_annotate=False):
111 ''' 112 Create an image from a file or a PIL Image, OpenCV Image, or numpy array. 113 114 @param data: this can be a numpy array, PIL image, or opencv image. 115 @param bw_annotate: generate a black and white image to make color annotations show up better 116 @return: an Image object instance 117 ''' 118 119 self.filename = None 120 self.pil = None 121 self.matrix2d = None 122 self.matrix3d = None 123 self.opencv = None 124 self.opencv2 = None 125 self.opencv2bw = None 126 self.annotated = None 127 self.bw_annotate = bw_annotate 128 129 # Convert floating point ipl images to numpy arrays 130 if isinstance(data,cv.iplimage) and data.nChannels == 3 and data.depth == 32: 131 w,h = cv.GetSize(data) 132 data = np.frombuffer(data.tostring(),dtype=np.float32) 133 data.shape = (h,w,3) 134 data = data.transpose((2,1,0)) 135 data = data[::-1,:,:] 136 137 # Convert floating point ipl images to numpy arrays 138 if isinstance(data,cv.iplimage) and data.nChannels == 1 and data.depth == 32: 139 w,h = cv.GetSize(data) 140 data = np.frombuffer(data.tostring(),dtype=np.float32) 141 data.shape = (h,w) 142 data = data.transpose((2,1,0)) 143 data = data[::-1,:,:] 144 145 # Numpy format 146 if isinstance(data,numpy.ndarray) and len(data.shape) == 2 and data.dtype != np.uint8: 147 self.type=TYPE_MATRIX_2D 148 self.matrix2d = data 149 150 self.width,self.height = self.matrix2d.shape 151 self.channels = 1 152 153 if self.matrix2d.dtype == numpy.float32: 154 self.depth=32 155 elif self.matrix2d.dtype == numpy.float64: 156 self.depth=64 157 else: 158 raise TypeError("Unsuppoted format for ndarray images: %s"%self.matrix2d.dtype) 159 160 # OpenCV2 gray scale format 161 elif isinstance(data,numpy.ndarray) and len(data.shape) == 2 and data.dtype == np.uint8: 162 self.type=TYPE_OPENCV2BW 163 self.opencv2bw = data 164 165 self.height,self.width = self.opencv2bw.shape 166 self.channels = 1 167 self.depth=8 168 169 # Numpy color format 170 elif isinstance(data,numpy.ndarray) and len(data.shape) == 3 and data.shape[0]==3 and data.dtype != np.uint8: 171 self.type=TYPE_MATRIX_RGB 172 self.matrix3d = data 173 self.channels=3 174 self.width = self.matrix3d.shape[1] 175 self.height = self.matrix3d.shape[2] 176 # set the types 177 if self.matrix3d.dtype == numpy.float32: 178 self.depth=32 179 elif self.matrix3d.dtype == numpy.float64: 180 self.depth=64 181 else: 182 raise TypeError("Unsuppoted format for ndarray images: %s"%self.matrix2d.dtype) 183 184 # OpenCV2 color format 185 elif isinstance(data,numpy.ndarray) and len(data.shape) == 3 and data.shape[2]==3 and data.dtype == np.uint8: 186 self.type=TYPE_OPENCV2 187 self.opencv2 = data 188 self.channels=3 189 self.width = self.opencv2.shape[1] 190 self.height = self.opencv2.shape[0] 191 self.depth=8 192 193 # Load as a pil image 194 elif isinstance(data,PIL.Image.Image) or type(data) == str: 195 if type(data) == str: 196 # Assume this is a filename 197 # TODO: Removing the filename causes errors in other unittest. 198 # Those errors should be corrected. 199 self.filename = data 200 data = PIL.Image.open(data) 201 202 self.type=TYPE_PIL 203 self.pil = data 204 self.width,self.height = self.pil.size 205 206 if self.pil.mode == 'L': 207 self.channels = 1 208 elif self.pil.mode == 'RGB': 209 self.channels = 3 210 #elif self.pil.mode == 'RGBA': 211 # 212 # self.pil = self.pil.convert('RGB') 213 # self.channels = 3 214 else: 215 self.pil.convert('RGB') 216 self.channels = 3 217 # raise TypeError("Unsuppoted format for PIL images: %s"%self.pil.mode) 218 219 self.depth = 8 220 221 # opencv format 222 elif isinstance(data,cv.iplimage): 223 self.type=TYPE_OPENCV 224 self.opencv=data 225 226 self.width = data.width 227 self.height = data.height 228 229 assert data.nChannels in (1,3) 230 self.channels = data.nChannels 231 232 assert data.depth == 8 233 self.depth = data.depth 234 235 # unknown type 236 else: 237 raise TypeError("Could not create from type: %s %s"%(data,type(data))) 238 239 self.size = (self.width,self.height) 240 self.data = data
241
242 - def asBW(self):
243 ''' 244 @return: a gray-scale version of this pyvision image 245 ''' 246 if self.matrix2d is None: 247 self._generateMatrix2D() 248 return Image(self.matrix2d)
249
250 - def asMatrix2D(self):
251 ''' 252 @return: the gray-scale image data as a two dimensional numpy array 253 ''' 254 if self.matrix2d is None: 255 self._generateMatrix2D() 256 return self.matrix2d
257
258 - def asMatrix3D(self):
259 ''' 260 @return: color image data as a 3D array with shape (3(rgb),w,h) 261 ''' 262 if self.matrix3d is None: 263 self._generateMatrix3D() 264 return self.matrix3d
265
266 - def asPIL(self):
267 ''' 268 @return: image data as a pil image 269 ''' 270 if self.pil is None: 271 self._generatePIL() 272 return self.pil
273
274 - def asOpenCV(self):
275 ''' 276 @return: the image data in an OpenCV format 277 ''' 278 if self.opencv is None: 279 self._generateOpenCV() 280 return self.opencv
281
282 - def asOpenCV2(self):
283 ''' 284 @return: the image data in an OpenCV format that is a numpy array of shape (h,w,3) of uint8 285 ''' 286 if self.opencv2 is None: 287 self._generateOpenCV2() 288 return self.opencv2
289
290 - def asOpenCV2BW(self):
291 ''' 292 @return: the image data in an OpenCV format that is a numpy array of shape (h,w,1) of uint8 293 ''' 294 if self.opencv2bw is None: 295 self._generateOpenCV2BW() 296 return self.opencv2bw
297
298 - def asOpenCVBW(self):
299 ''' 300 @return: the image data in an OpenCV one channel format 301 ''' 302 cvim = self.asOpenCV() 303 304 if cvim.nChannels == 1: 305 return cvim 306 307 elif cvim.nChannels == 3: 308 cvimbw = cv.CreateImage(cv.GetSize(cvim), cv.IPL_DEPTH_8U, 1); 309 cv.CvtColor(cvim, cvimbw, cv.CV_BGR2GRAY); 310 return cvimbw 311 312 else: 313 raise ValueError("Unsupported opencv image format: nChannels=%d"%cvim.nChannels)
314
315 - def asThermal(self,clip_negative=False):
316 ''' 317 @returns: a thermal colored representation of this image. 318 ''' 319 w,h = self.size 320 mat = self.asMatrix2D() 321 if clip_negative: 322 mat = mat*(mat > 0.0) 323 324 # Rescale 0.0 to 1.0 325 mat = mat - mat.min() 326 mat = mat / mat.max() 327 328 therm = np.zeros((3,w,h),dtype=np.float) 329 330 # Black to blue 331 mask = mat <= 0.1 332 therm[2,:,:] += mask*(0.5 + 0.5*mat/0.1) 333 334 # blue to yellow 335 mask = (mat > 0.10) & (mat <= 0.4) 336 tmp = (mat - 0.10) / 0.30 337 therm[2,:,:] += mask*(1.0-tmp) 338 therm[1,:,:] += mask*tmp 339 therm[0,:,:] += mask*tmp 340 341 # yellow to orange 342 mask = (mat > 0.4) & (mat <= 0.7) 343 tmp = (mat - 0.4) / 0.3 344 therm[2,:,:] += mask*0 345 therm[1,:,:] += mask*(1-0.5*tmp) 346 therm[0,:,:] += mask*1 347 348 # the orange to red 349 mask = (mat > 0.7) 350 tmp = (mat - 0.7) / 0.3 351 therm[2,:,:] += mask*0 352 therm[1,:,:] += mask*(0.5-0.5*tmp) 353 therm[0,:,:] += mask*1 354 355 return pv.Image(therm)
356 357
358 - def asAnnotated(self, as_type="PIL"):
359 ''' 360 @param as_type: Specify either "PIL" or "PV". If 361 "PIL" (default) then the return type is a PIL image. 362 If "PV", then the return type is a pyvision image, 363 where the annotations have been 'flattened' onto 364 the original source image. 365 @return: the PIL image used for annotation. 366 ''' 367 if self.annotated is None: 368 if self.bw_annotate: 369 # Make a black and white image that can be annotated with color. 370 self.annotated = self.asPIL().convert("L").copy().convert("RGB") 371 else: 372 # Annotate over color if available. 373 self.annotated = self.asPIL().copy().convert("RGB") 374 375 if as_type.upper() == "PV": 376 return pv.Image(self.annotated) 377 else: 378 return self.annotated
379
380 - def asHSV(self):
381 ''' 382 @return: an OpenCV HSV encoded image 383 ''' 384 cvim = self.asOpenCV() 385 dst = cv.CreateImage(cv.GetSize(cvim), cv.IPL_DEPTH_8U, 3) 386 cv.CvtColor(cvim, dst, cv.CV_BGR2HSV) 387 388 return dst
389 390
391 - def asLAB(self):
392 ''' 393 @return: an OpenCV LAB encoded image 394 ''' 395 cvim = self.asOpenCV() 396 dst = cv.CreateImage(cv.GetSize(cvim), cv.IPL_DEPTH_8U, 3) 397 cv.CvtColor(cvim, dst, cv.CV_BGR2Lab) 398 399 return dst
400
401 - def getExif(self,output='simple'):
402 ''' 403 This function returns the exif headers for an image. This only works 404 for images that have been read from disk. 405 406 @param output: select 'simple' or 'full'. 'full' output contains additional metadata. 407 @returns: a dictionary of EXIF data. 408 ''' 409 if self.type == TYPE_PIL and self.filename is not None: 410 result = {} 411 info = self.pil._getexif() 412 if info is None: 413 return None 414 415 # iterate through exif tags 416 for key,value in info.iteritems(): 417 tag = "ukn_%s"%key 418 # translate tags to text 419 if exif.EXIF_TAGS.has_key(key): 420 tag = exif.EXIF_TAGS[key][0] 421 datatype = exif.EXIF_TAGS[key][1] 422 category = exif.EXIF_TAGS[key][2] 423 description = exif.EXIF_TAGS[key][3] 424 # convert to floats 425 if isinstance(value,tuple) and len(value) == 2 and value[1] > 0: 426 value = float(value[0])/float(value[1]) 427 if output == 'simple': 428 result[tag] = value 429 else: 430 result[tag] = (value,key,datatype,category,description) 431 return result 432 else: 433 return None
434 435
436 - def annotateRect(self,rect,color='red', fill_color=None, alpha=1.0):
437 ''' 438 Draws a rectangle on the annotation image 439 440 @param rect: a rectangle of type Rect 441 @param color: defined as ('#rrggbb' or 'name') 442 @param fill_color: defined as per color, but indicates the color 443 used to fill the rectangle. Specify None for no fill. 444 @param alpha: Ignored if no fill. Otherwise, this value controls 445 how opaque the fill is. Specify 1.0 (default) for a fully opaque 446 fill, or 0.0 for fully transparent. A value of 0.3, for example, 447 would show a partially transparent filled rectangle over 448 the background image. 449 ''' 450 im = self.asAnnotated() 451 box = rect.box() 452 offset = (box[0],box[1]) 453 #this supports filling a rectangle that is semi-transparent 454 if fill_color: 455 (r,g,b) = PIL.ImageColor.getrgb(fill_color) 456 rect_img = PIL.Image.new('RGBA', (int(rect.w),int(rect.h)), (r,g,b,int(alpha*255))) 457 im.paste(rect_img,offset,mask=rect_img) #use 'paste' method to support transparency 458 459 #just draws the rect outline in the outline color 460 draw = PIL.ImageDraw.Draw(im) 461 draw.rectangle(box,outline=color,fill=None) 462 del draw
463
464 - def annotateImage(self,im,rect,color='red', fill_color=None):
465 ''' 466 Draws an image 467 468 @param im: the image to render 469 @param rect: a rectangle of type Rect 470 @param color: defined as ('#rrggbb' or 'name') 471 @param fill_color: defined as per color, but indicates the color 472 used to fill the rectangle. Specify None for no fill. 473 ''' 474 # Reduce the size of the image 475 thumb = im.thumbnail((rect.w,rect.h)) 476 x = int(rect.x + rect.w/2 - thumb.size[0]/2) 477 y = int(rect.y + rect.h/2 - thumb.size[1]/2) 478 479 # Get the annotated image buffer 480 pil = self.asAnnotated() 481 482 # Draw a rect 483 draw = PIL.ImageDraw.Draw(pil) 484 box = [rect.x,rect.y,rect.x+rect.w,rect.y+rect.h] 485 draw.rectangle(box,outline=None,fill=fill_color) 486 del draw 487 488 # Paste the image 489 pil.paste(im.asPIL(),(x,y)) 490 491 # Draw a rect over the top 492 draw = PIL.ImageDraw.Draw(pil) 493 box = [rect.x,rect.y,rect.x+rect.w,rect.y+rect.h] 494 draw.rectangle(box,outline=color,fill=None) 495 del draw
496
497 - def annotateThickRect(self,rect,color='red',width=5):
498 ''' 499 Draws a rectangle on the annotation image 500 501 @param rect: a rectangle of type Rect 502 @param color: defined as ('#rrggbb' or 'name') 503 ''' 504 # get the image buffer 505 im = self.asAnnotated() 506 draw = PIL.ImageDraw.Draw(im) 507 x,y,w,h = [rect.x,rect.y,rect.w,rect.h] 508 509 # Draw individual lines 510 line = [x,y,x+w,y] 511 draw.line(line,fill=color,width=width) 512 line = [x,y,x,y+h] 513 draw.line(line,fill=color,width=width) 514 line = [x,y+h,x+w,y+h] 515 draw.line(line,fill=color,width=width) 516 line = [x+w,y,x+w,y+h] 517 draw.line(line,fill=color,width=width) 518 del draw
519
520 - def annotateEllipse(self,rect,color='red'):
521 ''' 522 Draws an ellipse on the annotation image 523 524 @param rect: the bounding box of the elipse of type Rect 525 @param color: defined as ('#rrggbb' or 'name') 526 ''' 527 im = self.asAnnotated() 528 draw = PIL.ImageDraw.Draw(im) 529 box = [rect.x,rect.y,rect.x+rect.w,rect.y+rect.h] 530 draw.ellipse(box,outline=color) 531 del draw
532
533 - def annotateLine(self,point1,point2,color='red',width=1):
534 ''' 535 Draws a line from point1 to point2 on the annotation image 536 537 @param point1: the starting point as type Point 538 @param point2: the ending point as type Point 539 @param color: defined as ('#rrggbb' or 'name') 540 ''' 541 im = self.asAnnotated() 542 draw = PIL.ImageDraw.Draw(im) 543 line = [point1.X(),point1.Y(),point2.X(),point2.Y()] 544 draw.line(line,fill=color,width=width) 545 del draw
546
547 - def annotateLines(self,points,color='red',width=1):
548 ''' 549 Draws a line from point1 to point2 on the annotation image 550 551 @param point1: the starting point as type Point 552 @param point2: the ending point as type Point 553 @param color: defined as ('#rrggbb' or 'name') 554 ''' 555 n = len(points)-1 556 for i in range(n): 557 self.annotateLine(points[i],points[i+1],color=color,width=width)
558
559 - def annotateMask(self,mask,color='red'):
560 ''' 561 Shades the contents of a mask. 562 563 @param mask: a numpy array showing the mask. 564 @param color: defined as ('#rrggbb' or 'name') 565 ''' 566 im = self.asAnnotated() 567 draw = PIL.ImageDraw.Draw(im) 568 pil = pv.Image(1.0*mask).asPIL() 569 pil = pil.convert('1') 570 draw.bitmap((0,0), pil, fill=color) 571 del draw
572
573 - def annotatePolygon(self,points,color='red',width=1,fill=None):
574 ''' 575 Draws a line from point1 to point2 on the annotation image 576 577 @param points: a list of pv points to be plotted 578 @param color: defined as ('#rrggbb' or 'name') 579 @param width: the line width 580 ''' 581 # Fill the center 582 if fill is not None: 583 im = self.asAnnotated() 584 draw = PIL.ImageDraw.Draw(im) 585 poly = [(point.X(),point.Y()) for point in points] 586 draw.polygon(poly,outline=None,fill=fill) 587 del draw 588 589 # Draw lines 590 if color is not None: 591 n = len(points) 592 for i in range(n): 593 j = (i+1)%n 594 self.annotateLine(points[i],points[j],color=color,width=width)
595
596 - def annotatePoint(self,point,color='red'):
597 ''' 598 Marks a point in the annotation image using a small circle 599 600 @param point: the point to mark as type Point 601 @param color: defined as ('#rrggbb' or 'name') 602 ''' 603 im = self.asAnnotated() 604 draw = PIL.ImageDraw.Draw(im) 605 box = [point.X()-3,point.Y()-3,point.X()+3,point.Y()+3] 606 draw.ellipse(box,outline=color) 607 del draw
608
609 - def annotatePoints(self,points,color='red'):
610 ''' 611 Marks a point in the annotation image using a small circle 612 613 @param point: the point to mark as type Point 614 @param color: defined as ('#rrggbb' or 'name') 615 ''' 616 im = self.asAnnotated() 617 draw = PIL.ImageDraw.Draw(im) 618 for point in points: 619 box = [point.X()-3,point.Y()-3,point.X()+3,point.Y()+3] 620 draw.ellipse(box,outline=color) 621 del draw
622
623 - def annotateCircle(self,point, radius=3, color='red',fill=None):
624 ''' 625 Marks a circle in the annotation image 626 627 @param point: the center of the circle as type Point 628 @param radius: the radius of the circle 629 @param color: defined as ('#rrggbb' or 'name') 630 ''' 631 im = self.asAnnotated() 632 draw = PIL.ImageDraw.Draw(im) 633 box = [point.X()-radius,point.Y()-radius,point.X()+radius,point.Y()+radius] 634 draw.ellipse(box,outline=color,fill=fill) 635 del draw
636
637 - def annotateArc(self,point, radius=3, startangle=0, endangle=360, color='red'):
638 ''' 639 Draws a circular arc on the image. 640 @param point: the center of the circle as type Point 641 @param radius: the radius of the circle 642 @param startangle: the starting angle of the arc segment to be drawn, in degrees 643 @param endangle: the ending angle in degrees. Arc will be drawn clockwise from 644 starting angle to ending angle. 645 @param color: defined as ('#rrggbb' or 'name') 646 ''' 647 im = self.asAnnotated() 648 draw = PIL.ImageDraw.Draw(im) 649 box = [int(point.X()-radius),int(point.Y()-radius), 650 int(point.X()+radius),int(point.Y()+radius)] 651 draw.arc(box, int(startangle), int(endangle), fill=color) 652 del draw
653
654 - def annotateLabel(self,point,label,color='red',mark=False, font=None, background=None):
655 ''' 656 Marks a point in the image with text 657 658 @param point: the point to mark as type Point 659 @param label: the text to use as a string 660 @param color: defined as ('#rrggbb' or 'name') 661 @param mark: of True or ['right', 'left', 'below', or 'above','centered'] then also mark the point with a small circle 662 @param font: An optional PIL.ImageFont font object to use. Alternatively, specify an integer and the label 663 will use Arial font of that size. If None, then the default is used. 664 @param background: An optional color that will be used to draw a rectangular background underneath the text. 665 ''' 666 # Get the image buffer 667 im = self.asAnnotated() 668 draw = PIL.ImageDraw.Draw(im) 669 670 # Load the font 671 if font is None: 672 font = ImageFont.load_default() 673 elif isinstance(font,int): 674 font = ImageFont.truetype(pv.FONT_ARIAL, font) 675 676 # Compute the size 677 tw,th = draw.textsize(label, font=font) 678 679 # Select the position relative to the point 680 if mark in [True, 'right']: 681 textpt = pv.Point(point.X()+5,point.Y()-th/2) 682 box = [point.X()-3,point.Y()-3,point.X()+3,point.Y()+3] 683 elif mark in ['left']: 684 textpt = pv.Point(point.X()-tw-5,point.Y()-th/2) 685 box = [point.X()-3,point.Y()-3,point.X()+3,point.Y()+3] 686 elif mark in ['below']: # 687 textpt = pv.Point(point.X()-tw/2,point.Y()+5) 688 box = [point.X()-3,point.Y()-3,point.X()+3,point.Y()+3] 689 elif mark in ['above']: 690 textpt = pv.Point(point.X()-tw/2,point.Y()-th-5) 691 box = [point.X()-3,point.Y()-3,point.X()+3,point.Y()+3] 692 elif mark in ['centered']: 693 textpt = pv.Point(point.X()-tw/2,point.Y()-th/2) 694 else: 695 textpt = point 696 697 # Fill in the background 698 if background is not None: 699 point2 = pv.Point( textpt.x + tw, textpt.y+th) 700 draw.rectangle([textpt.asTuple(), point2.asTuple()], fill=background) 701 702 # Render the text 703 draw.text([textpt.x,textpt.y],label,fill=color, font=font) 704 705 if mark not in [False,None,'centered']: 706 draw.ellipse(box,outline=color) 707 708 del draw
709 710
711 - def annotateDot(self,point,color='red'):
712 ''' 713 Like L{annotatePoint} but only draws a point on the given pixel. 714 This is useful to avoid clutter if many points are being annotated. 715 716 @param point: the point to mark as type Point 717 @param color: defined as ('#rrggbb' or 'name') 718 ''' 719 im = self.asAnnotated() 720 draw = PIL.ImageDraw.Draw(im) 721 draw.point([point.X(),point.Y()],fill=color) 722 del draw
723 724
725 - def valueNormalize(self):
726 '''TODO: Deprecated remove this sometime.''' 727 print "WARNING: Image.valueNormalize has been deprecated." 728 return self.normalize()
729 730
731 - def getType(self):
732 '''Return the type of the image.''' 733 return self.type
734 735
736 - def normalize(self):
737 ''' Equalize and normalize the image. ''' 738 import PIL.ImageOps 739 # Create a copy 740 pil = self.asPIL().copy() 741 742 # Equalize 743 pil = PIL.ImageOps.equalize(pil.convert('L')) 744 self.pil = pil 745 self.matrix2d = None 746 747 # Normalize 748 mat = self.asMatrix2D() 749 mean = mat.mean() 750 std = mat.std() 751 mat -= mean 752 mat /= std 753 self.matrix2d=mat
754 755
756 - def equalize(self, bw=True):
757 ''' Equalize the image ''' 758 import PIL.ImageOps 759 pil = self.asPIL().copy() 760 if bw: 761 pil = PIL.ImageOps.equalize(pil.convert('L')) 762 else: 763 pil = PIL.ImageOps.equalize(pil) 764 return pv.Image(pil)
765 766
767 - def _generateMatrix2D(self):
768 ''' 769 Create a matrix version of the image. 770 ''' 771 data_buffer = self.toBufferGray(32) 772 self.matrix2d = numpy.frombuffer(data_buffer,numpy.float32).reshape(self.height,self.width).transpose()
773 774
775 - def _generateMatrix3D(self):
776 ''' 777 Create a matrix version of the image. 778 ''' 779 data_buffer = self.toBufferRGB(32) 780 self.matrix3d = numpy.frombuffer(data_buffer,numpy.float32).reshape(self.height,self.width,3).transpose()
781
782 - def _generatePIL(self):
783 ''' 784 Create a PIL version of the image 785 ''' 786 if self.channels == 1: 787 try: 788 # PILLOW 789 self.pil = PIL.Image.frombytes("L",self.size,self.toBufferGray(8)) 790 except: 791 # PIL 792 self.pil = PIL.Image.fromstring("L",self.size,self.toBufferGray(8)) 793 elif self.channels == 3: 794 try: 795 self.pil = PIL.Image.frombytes("RGB",self.size,self.toBufferRGB(8)) 796 except: 797 self.pil = PIL.Image.fromstring("RGB",self.size,self.toBufferRGB(8)) 798 else: 799 raise NotImplementedError("Cannot convert image from type: %s"%self.type)
800
801 - def _generateOpenCV(self):
802 ''' 803 Create a color opencv representation of the image. 804 TODO: The OpenCV databuffer seems to be automatically swapped from RGB to BGR. This is counter intuitive. 805 ''' 806 807 w,h = self.size 808 # generate a grayscale opencv image 809 if self.channels == 1: 810 gray = cv.CreateImage((w,h),cv.IPL_DEPTH_8U,1) 811 cv.SetData(gray,self.toBufferGray(8)) 812 self.opencv = gray 813 # Generate a color opencv image 814 elif self.channels == 3: 815 rgb = cv.CreateImage((w,h),cv.IPL_DEPTH_8U,3) 816 bgr = cv.CreateImage((w,h),cv.IPL_DEPTH_8U,3) 817 cv.SetData(rgb, self.toBufferRGB(8)) 818 # convert from RGB to BGR 819 cv.CvtColor(rgb,bgr,cv.CV_RGB2BGR) 820 self.opencv=bgr 821 else: 822 raise NotImplementedError("Cannot convert image from type: %s"%self.type)
823
824 - def _generateOpenCV2(self):
825 ''' 826 Create a matrix version of the image compatible with OpenCV 2 (cv2) in BGR format. 827 ''' 828 data_buffer = self.toBufferRGB(8) 829 self.opencv2 = cv2.cvtColor(numpy.frombuffer(data_buffer,numpy.uint8).reshape(self.height,self.width,3),cv2.COLOR_RGB2BGR)
830 831
832 - def _generateOpenCV2BW(self):
833 ''' 834 Create a matrix version of the image compatible with OpenCV 2 (cv2) in BGR format. 835 ''' 836 data_buffer = self.toBufferGray(8) 837 self.opencv2bw = numpy.frombuffer(data_buffer,numpy.uint8).reshape(self.height,self.width)
838 839 840
841 - def toBufferGray(self,depth):
842 ''' 843 @param depth: Use 8, 32, or 64, to specify the bit depth of the pixels. 844 @return: the image data as a binary python string. 845 ''' 846 image_buffer = None 847 if self.type == TYPE_PIL: 848 # Convert to gray and then get buffer 849 pil = self.pil 850 if pil.mode != 'L': 851 pil = pil.convert('L') 852 try: 853 # PILLOW 854 image_buffer = pil.tobytes() 855 except: 856 # PIL 857 image_buffer = pil.tostring() 858 elif self.type == TYPE_MATRIX_2D: 859 # Just get the buffer 860 image_buffer = self.matrix2d.transpose().tostring() 861 elif self.type == TYPE_OPENCV2BW: 862 # Just get the buffer 863 image_buffer = self.opencv2bw.tostring() 864 elif self.type == TYPE_OPENCV2: 865 # Convert to gray then get buffer 866 tmp = cv2.cvtColor(self.opencv2, cv2.cv.CV_BGR2GRAY) 867 image_buffer = tmp.tostring() 868 elif self.type == TYPE_MATRIX_RGB: 869 # Convert to gray 870 mat = self.matrix3d 871 mat = LUMA[0]*mat[0] + LUMA[1]*mat[1] + LUMA[2]*mat[2] 872 image_buffer = mat.transpose().tostring() 873 elif self.type == TYPE_OPENCV: 874 if self.channels == 1: 875 # Just get buffer 876 image_buffer = self.opencv.tostring() 877 elif self.channels == 3: 878 # Convert to gray 879 w,h = self.width,self.height 880 gray = cv.CreateImage((w,h),cv.IPL_DEPTH_8U,1) 881 cv.CvtColor( self.opencv, gray, cv.CV_BGR2GRAY ); 882 image_buffer = gray.tostring() 883 else: 884 raise TypeError("Operation not supported for image type.") 885 else: 886 raise TypeError("Operation not supported for image type.") 887 888 # Buffer should now be created 889 assert image_buffer 890 891 # Make sure the depth is correct 892 if depth == self.depth: 893 return image_buffer 894 895 else: 896 types = {8:numpy.uint8,32:numpy.float32,64:numpy.float64} 897 898 # convert the image_buffer to data 899 data = numpy.frombuffer(image_buffer,types[self.depth]) 900 901 if depth==8: 902 # Make sure the data is in a valid range 903 max_value = data.max() 904 min_value = data.min() 905 data_range = max_value - min_value 906 if max_value <= 255 and min_value >= 0 and data_range >= 150: 907 # assume the values are already in a good range for the 908 # 8 bit image 909 pass 910 else: 911 # Rescale the values from 0 to 255 912 if max_value == min_value: 913 max_value = min_value+1 914 data = (255.0/(max_value-min_value))*(data-min_value) 915 916 data = data.astype(types[depth]) 917 return data.tostring()
918 919
920 - def toBufferRGB(self,depth):
921 ''' 922 returns the image data as a binary python string. 923 ''' 924 image_buffer = None 925 if self.type == TYPE_PIL: 926 # Convert to rgb then get buffer 927 pil = self.pil 928 if pil.mode != 'RGB': 929 pil = pil.convert('RGB') 930 try: 931 # PILLOW 932 image_buffer = pil.tobytes() 933 except: 934 # PIL 935 image_buffer = pil.tostring() 936 elif self.type == TYPE_MATRIX_2D: 937 # Convert to color 938 mat = self.matrix2d.transpose() 939 tmp = np.zeros((3,self.height,self.width),numpy.float32) 940 tmp[0,:] = mat 941 tmp[1,:] = mat 942 tmp[2,:] = mat 943 image_buffer = mat.tostring() 944 elif self.type == TYPE_OPENCV2BW: 945 # Convert to color 946 tmp = cv2.cvtColor(self.opencv2bw, cv2.cv.CV_GRAY2RGB) 947 image_buffer = tmp.tostring() 948 elif self.type == TYPE_OPENCV2: 949 # Convert BGR to RGB 950 tmp = cv2.cvtColor(self.opencv2, cv2.cv.CV_BGR2RGB) 951 image_buffer = tmp.tostring() 952 elif self.type == TYPE_MATRIX_RGB: 953 # Just get buffer 954 mat = self.matrix3d.transpose() 955 image_buffer = mat.tostring() 956 elif self.type == TYPE_OPENCV: 957 # Convert color BGR to RGB 958 w,h = self.width,self.height 959 if self.channels == 3: 960 rgb = cv.CreateImage((w,h),cv.IPL_DEPTH_8U,3) 961 cv.CvtColor( self.opencv, rgb, cv.CV_BGR2RGB ); 962 image_buffer = rgb.tostring() 963 elif self.channels == 1: 964 rgb = cv.CreateImage((w,h),cv.IPL_DEPTH_8U,3) 965 cv.CvtColor( self.opencv, rgb, cv.CV_GRAY2RGB ); 966 image_buffer = rgb.tostring() 967 else: 968 # Handle type errors 969 raise TypeError("Operation not supported for image type.") 970 else: 971 # Handle unsupported 972 raise TypeError("Operation not supported for image type.") 973 974 assert image_buffer 975 976 # Correct depth issues 977 if depth == self.depth: 978 return image_buffer 979 980 else: 981 types = {8:numpy.uint8,32:numpy.float32,64:numpy.float64} 982 983 # convert the image_buffer to data 984 data = numpy.frombuffer(image_buffer,types[self.depth]) 985 986 if depth==8: 987 # Make sure the data is in a valid range 988 max_value = data.max() 989 min_value = data.min() 990 data_range = max_value - min_value 991 if max_value <= 255 and min_value >= 0 and data_range >= 50: 992 # assume the values are already in a good range for the 993 # 8 bit image 994 pass 995 else: 996 # Rescale the values from 0 to 255 997 if max_value == min_value: 998 max_value = min_value+1 999 data = (255.0/(max_value-min_value))*(data-min_value) 1000 1001 data = data.astype(types[depth]) 1002 return data.tostring()
1003 1004
1005 - def toBufferRGBA(self,depth):
1006 ''' 1007 returns the image data as a binary python string. 1008 TODO: Not yet implemented 1009 '''
1010
1011 - def thumbnail(self, newSize):
1012 ''' Returns a resized version of the image that fits in new_size but preserves the aspect ratio. 1013 1014 @param newSize: tuple (new_width, new_height) 1015 @returns: a new pyvision image that is the resized version of this image. 1016 ''' 1017 w,h = self.size 1018 s1 = float(newSize[0])/w 1019 s2 = float(newSize[1])/h 1020 s = min(s1,s2) 1021 return self.scale(s)
1022 1023
1024 - def resize(self, newSize, **kwargs):
1025 ''' Returns a resized version of the image. This is a convenience function. 1026 For more control, look at the Affine class for arbitrary transformations. 1027 @param newSize: tuple (new_width, new_height) 1028 @returns: a new pyvision image that is the resized version of this image. 1029 ''' 1030 tmp = self.asPIL() 1031 if newSize[0] < self.size[0] or newSize[1] < self.size[1]: 1032 #because at least one dimension is being shrinked, we need to use ANTIALIAS filter 1033 tmp = tmp.resize(newSize, ANTIALIAS) 1034 else: 1035 #use bicubic interpolation 1036 tmp = tmp.resize(newSize, BICUBIC) 1037 1038 return pyvision.Image(tmp,**kwargs)
1039
1040 - def scale(self, scale):
1041 ''' Returns a scaled version of the image. This is a convenience function. 1042 For more control, look at the Affine class for arbitrary transformations. 1043 @param scale: a float indicating the scale factor 1044 @returns: a new pyvision image that is the scaled version of this image. 1045 ''' 1046 assert scale > 0.0 1047 w,h = self.size 1048 new_size = (int(round(scale*w)),int(round(scale*h))) 1049 return self.resize(new_size)
1050
1051 - def copy(self):
1052 ''' 1053 Returns a new pv.Image which is a copy of (only) the current image. 1054 Other internal data stored by the current pv.Image will NOT be copied. 1055 This method uses cv.CloneImage so that the underlying image data will be 1056 disconnected from the original data. (Deep copy) 1057 ''' 1058 imgdat = self.asOpenCV() 1059 imgdat2 = cv.CloneImage(imgdat) 1060 return pv.Image(imgdat2)
1061
1062 - def crop(self, rect, size=None, interpolation=None, return_affine=False):
1063 ''' 1064 Crops an image to the given rectangle. Rectangle parameters are rounded to nearest 1065 integer values. High quality resampling. The default behavior is to use cv.GetSubRect 1066 to crop the image. This returns a slice the OpenCV image so modifying the resulting 1067 image data will also modify the data in this image. If a size is provide a new OpenCV 1068 image is created for that size and cv.Resize is used to copy the image data. If the 1069 bounds of the rectangle are outside the image, an affine transform (pv.AffineFromRect) 1070 is used to produce the croped image to properly handle regions outside the image. 1071 In this case the downsampling quality may not be as good. # 1072 1073 @param rect: a Rectangle defining the region to be cropped. 1074 @param size: a new size for the returned image. If None the result is not resized. 1075 @param interpolation: None = Autoselect or one of CV_INTER_AREA, CV_INTER_NN, CV_INTER_LINEAR, CV_INTER_BICUBIC 1076 @param return_affine: If True, also return an affine transform that can be used to transform points. 1077 @returns: a cropped version of the image or if return affine a tuple of (image,affine) 1078 @rtype: pv.Image 1079 ''' 1080 # Notes: pv.Rect(0,0,w,h) should return the entire image. Since pixel values 1081 # are indexed by zero this means that upper limits are not inclusive: x from [0,w) 1082 # and y from [0,h) 1083 x,y,w,h = rect.asTuple() 1084 1085 x = int(np.round(x)) 1086 y = int(np.round(y)) 1087 w = int(np.round(w)) 1088 h = int(np.round(h)) 1089 1090 # Check the bounds for cropping 1091 if x < 0 or y < 0 or x+w > self.size[0] or y+h > self.size[1]: 1092 if size is None: 1093 size = (w,h) 1094 1095 affine = pv.AffineFromRect(pv.Rect(x,y,w,h),size) 1096 im = affine(self) 1097 if return_affine: 1098 return im,affine 1099 else: 1100 return im 1101 1102 # Get the image as opencv 1103 cvim = self.asOpenCV() 1104 1105 # Set up ROI 1106 subim = cv.GetSubRect(cvim,(x,y,w,h)) 1107 1108 affine = pv.AffineTranslate(-x,-y,(w,h)) 1109 1110 if size is None: 1111 size = (w,h) 1112 1113 # Copy to new image 1114 new_image = cv.CreateImage(size,cvim.depth,cvim.nChannels) 1115 if interpolation is None: 1116 1117 if size[0] < w or size[1] < y: 1118 # Downsampling so use area interpolation 1119 interpolation = cv.CV_INTER_AREA 1120 else: 1121 # Upsampling so use linear 1122 interpolation = cv.CV_INTER_CUBIC 1123 1124 # Resize to the correct size 1125 cv.Resize(subim,new_image,interpolation) 1126 1127 affine = pv.AffineNonUniformScale(float(size[0])/w,float(size[1])/h,size)*affine 1128 1129 # Return the result as a pv.Image 1130 if return_affine: 1131 return pv.Image(new_image),affine 1132 else: 1133 return pv.Image(new_image)
1134 1135
1136 - def save(self,filename,annotations=False):
1137 ''' 1138 Save the image to a file. This is performed by converting to PIL and 1139 then saving to a file based on on the extension. 1140 ''' 1141 if filename[-4:] == ".raw": 1142 # TODO: save as a matrix 1143 raise NotImplementedError("Cannot save as a matrix") 1144 #elif filename[-4:] == ".mat": 1145 # TODO: save as a matlab file 1146 # raise NotImplementedError("Cannot save in matlab format") 1147 else: 1148 if annotations: 1149 self.asAnnotated().save(filename) 1150 else: 1151 self.asPIL().save(filename)
1152 1153
1154 - def show(self, window=None, pos=None, delay=0, size=None):
1155 ''' 1156 Displays the annotated version of the image using OpenCV highgui 1157 @param window: the name of the highgui window to use, if one already exists by this name, 1158 or it will create a new highgui window with this name. 1159 @param pos: if a new window is being created, the (x,y) coordinate for the new window 1160 @param delay: A delay in milliseconds to wait for keyboard input (passed to cv.WaitKey). 1161 0 delays indefinitely, 30 is good for presenting a series of images like a video. 1162 For performance reasons, namely when using the same window to display successive 1163 frames of video, we don't want to tear-down and re-create the window each time. 1164 Thus the window frame will persist beyond the scope of the call to img.show(). The window 1165 will disappear after the program exits, or it can be destroyed with a call to cv.DestroyWindow. 1166 @param size: Optional output size for image, None=native size. 1167 @returns: the return value of the cv.WaitKey call. 1168 ''' 1169 if window==None and pv.runningInNotebook() and 'pylab' in globals().keys(): 1170 # If running in notebook, then try to display the image inline. 1171 1172 if size is None: 1173 size = self.size 1174 1175 # Constrain the size of the output 1176 max_dim = max(size[0],size[1]) 1177 1178 if max_dim > 800: 1179 scale = 800.0/max_dim 1180 size = (int(scale*size[0]),int(scale*size[1])) 1181 1182 w,h = size 1183 1184 # TODO: Cant quite figure out how figsize works and how to set it to native pixels 1185 #pylab.figure() 1186 IPython.core.pylabtools.figsize(1.25*w/72.0,1.25*h/72.0) #@UndefinedVariable 1187 pylab.figure() 1188 pylab.imshow(self.asAnnotated(),origin='upper',aspect='auto') 1189 1190 else: 1191 # Otherwise, use an opencv window 1192 if window is None: 1193 window = "PyVisionImage" 1194 1195 # Create the window 1196 cv.NamedWindow(window) 1197 1198 # Set the location 1199 if pos is not None: 1200 cv.MoveWindow(window, pos[0], pos[1]) 1201 1202 # Resize the image. 1203 if size is not None: 1204 x = pyvision.Image(self.asAnnotated().resize(size) ) 1205 else: 1206 x = pyvision.Image(self.asAnnotated()) 1207 1208 # Display the result 1209 cv.ShowImage(window, x.asOpenCV() ) 1210 key = cv.WaitKey(delay=delay) 1211 del x 1212 return key
1213 1214
1215 - def __repr__(self):
1216 1217 return "pv.Image(w=%d,h=%d,c=%d,type=%s)"%(self.width,self.height,self.channels,self.type)
1218 1219
1220 -def OpenCVToNumpy(cvmat):
1221 ''' 1222 Convert an OpenCV matrix to a numpy matrix. 1223 1224 Based on code from: http://opencv.willowgarage.com/wiki/PythonInterface 1225 ''' 1226 depth2dtype = { 1227 cv.CV_8U: 'uint8', 1228 cv.CV_8S: 'int8', 1229 cv.CV_16U: 'uint16', 1230 cv.CV_16S: 'int16', 1231 cv.CV_32S: 'int32', 1232 cv.CV_32F: 'float32', 1233 cv.CV_64F: 'float64', 1234 } 1235 1236 # Check the size and channels 1237 assert cvmat.channels == 1 1238 r = cvmat.rows 1239 c = cvmat.cols 1240 1241 # Convert to numpy 1242 a = np.fromstring( 1243 cvmat.tostring(), 1244 dtype=depth2dtype[cvmat.type], 1245 count=r*c) 1246 a.shape = (r,c) 1247 return a
1248 1249
1250 -def NumpyToOpenCV(a):
1251 ''' 1252 Convert a numpy matrix to an OpenCV matrix. 1253 1254 Based on code from: http://opencv.willowgarage.com/wiki/PythonInterface 1255 ''' 1256 dtype2depth = { 1257 'uint8': cv.CV_8U, 1258 'int8': cv.CV_8S, 1259 'uint16': cv.CV_16U, 1260 'int16': cv.CV_16S, 1261 'int32': cv.CV_32S, 1262 'float32': cv.CV_32F, 1263 'float64': cv.CV_64F, 1264 } 1265 1266 # Check the size 1267 assert len(a.shape) == 2 1268 r,c = a.shape 1269 1270 # Convert to opencv 1271 cv_im = cv.CreateMat(r,c,dtype2depth[str(a.dtype)]) 1272 cv.SetData(cv_im, a.tostring()) 1273 return cv_im
1274