Package pyvision :: Package analysis :: Module plot
[hide private]
[frames] | no frames]

Source Code for Module pyvision.analysis.plot

  1  ''' 
  2  A very simple 2D plotting package that outputs images. 
  3   
  4  @author: bolme 
  5  ''' 
  6   
  7  import pyvision as pv 
  8  import numpy as np 
  9  import os.path 
 10  import PIL.ImageFont 
 11  import PIL.ImageDraw 
 12  random = np.random 
 13  import StringIO 
 14  import sys 
 15   
 16  arial_path = os.path.join(pv.__path__[0],'config','Arial.ttf') 
 17  huge_font = PIL.ImageFont.truetype(arial_path, 36) 
 18  large_font = PIL.ImageFont.truetype(arial_path, 18) 
 19  small_font = PIL.ImageFont.truetype(arial_path, 12) 
 20   
 21   
22 -def drawLabel(plot_image, pt, label, size='small',align='center',rotate=False,color='black'):
23 ''' 24 @param plot_image: a PIL image to use as a plot_image 25 ''' 26 font = small_font 27 if size == 'large': 28 font = large_font 29 elif size == "huge": 30 font = huge_font 31 32 # Render the mask 33 size = font.getsize(label) 34 im = PIL.Image.new("L",size,'black') 35 draw = PIL.ImageDraw.Draw(im) 36 draw.text((0,0),label,fill='white',font=font) 37 del draw 38 39 # Rotate 40 if rotate: 41 im = im.transpose(PIL.Image.ROTATE_90) 42 43 # Render the text 44 x,y = pt 45 w,h = im.size 46 if align == 'center': 47 x = x - w/2 48 y = y - h/2 49 elif align == 'left': 50 x = x - w 51 y = y - h/2 52 elif align == 'right': 53 x = x 54 y = y - h/2 55 elif align == 'above': 56 x = x - w/2 57 y = y - h 58 elif align == 'bellow': 59 x = x - w/2 60 y = y 61 else: 62 raise ValueError("Unknown alignment: %s"%align) 63 draw = PIL.ImageDraw.Draw(plot_image) 64 draw.bitmap((x,y),im,fill=color) 65 del draw
66 #plot_image.(color,(x,y,x+w,y+h),im) 67 68
69 -def dataToFormatedList(data):
70 out = "" 71 # group data into rows of 8 72 for i in range(0,len(data),8): 73 part = data[i:i+8] 74 for each in part: 75 out += "%s,"%each 76 if i + 8 >= len(data): 77 # Finish 78 out = out[:-1] # Remove the last comma 79 else: 80 # Continue with next row 81 out += "\n " 82 return out
83
84 -class Label:
85 - def __init__(self,point,label,size='small',align='center',rotate=False,color='black'):
86 self.point = point[:2] 87 self.label = label 88 self.size = size 89 self.align = align 90 self.rotate = rotate 91 self.color = color
92
93 - def draw(self,plot,plot_image,bounds):
94 x,y = self.point 95 x = plot.x(x,bounds) 96 y = plot.y(y,bounds) 97 drawLabel(plot_image,[x,y],self.label,size=self.size,align=self.align,rotate=self.rotate,color=self.color)
98
99 - def drawR(self,f):
100 sys.stderr.write("WARNING> Plot draw label is not implemented for R.\n")
101
102 - def range(self):
103 x,y = self.point 104 105 return x,x,y,y
106 107 108
109 -class Points:
110 - def __init__(self,points,graphic_type,color='black',shape=0,size=3,label=None,lty=None,width=1):
111 '''''' 112 self.graphic_type = graphic_type 113 self.points = points 114 self.color = color 115 self.shape = shape 116 self.size = size 117 self.label = label 118 self.width = width 119 self.lty = lty
120 121
122 - def draw(self,plot,plot_image,bounds):
123 '''''' 124 points = [ (plot.x(x,bounds),plot.y(y,bounds)) for x,y in self.points] 125 126 if self.graphic_type == 'lines': 127 self.drawCurve(points,plot_image) 128 elif self.graphic_type == 'points': 129 self.drawPoints(points,plot_image) 130 elif self.graphic_type == 'polygon': 131 self.drawPolygon(points,plot_image) 132 else: 133 raise ValueError("unknown graphic_type: %s"%(self.graphic_type,))
134
135 - def drawR(self,f):
136 '''''' 137 xpoints = [ float(x) for x,y in self.points] 138 ypoints = [ float(y) for x,y in self.points] 139 140 f.write("xpoints=c(%s)\n"%(dataToFormatedList(xpoints),)) 141 f.write("\n") 142 f.write("ypoints=c(%s)\n"%(dataToFormatedList(ypoints),)) 143 f.write("\n") 144 145 if self.graphic_type == 'lines': 146 f.write("lines(xpoints,ypoints,lty=%s,col='%s',lwd='%s')"%(self.lty,self.color,self.width)) 147 elif self.graphic_type == 'points': 148 f.write("points(xpoints,ypoints,col='%s',pch=%s,cex=%s,lwd=%s)"%(self.color,repr(self.shape),self.size/3.0,self.width)) 149 elif self.graphic_type == 'polygon': 150 xpoints.append(xpoints[0]) 151 ypoints.append(ypoints[0]) 152 f.write("lines(xpoints,ypoints,lty=%s,col='%s',lwd='%s')"%(self.lty,self.color,self.width)) 153 else: 154 raise ValueError("unknown graphic_type: %s"%(self.graphic_type,))
155
156 - def drawCurve(self,points,plot_image):
157 draw = PIL.ImageDraw.Draw(plot_image) 158 prev_x,prev_y = points[0] 159 for x,y in points[1:]: 160 draw.line([prev_x,prev_y,x,y],fill=self.color,width=self.width) 161 prev_x = x 162 prev_y = y 163 del draw
164
165 - def drawPolygon(self,points,plot_image):
166 if len(points) < 3: 167 return # No Op 168 draw = PIL.ImageDraw.Draw(plot_image) 169 draw.polygon(points, fill=self.color) 170 del draw
171
172 - def drawPoints(self,points,plot_image):
173 ''' Render points on the plot ''' 174 shape = self.shape 175 size = self.size 176 color = self.color 177 178 draw = PIL.ImageDraw.Draw(plot_image) 179 180 for i in range(len(points)): 181 x,y = points[i] 182 183 if isinstance(shape,int) and shape >= 0: 184 shape = (shape) % 21 185 186 if shape == '.': 187 # fill in a pixel 188 draw.point((x,y),fill=color) 189 elif shape == 0: 190 # hollow square 191 draw.rectangle((x-size,y-size,x+size,y+size),outline=color,fill=None) 192 elif shape == 1: 193 # hollow circle 194 draw.ellipse((x-size,y-size,x+size,y+size),outline=color,fill=None) 195 elif shape == 2: 196 # hollow triangle 197 draw.polygon((x,y-size,x-size,y+size,x+size,y+size,x,y-size),outline=color,fill=None) 198 elif shape == 3: 199 # plus 200 draw.line((x-size,y,x+size,y),fill=color) 201 draw.line((x,y-size,x,y+size),fill=color) 202 elif shape == 4: 203 # cross 204 draw.line((x-size,y-size,x+size,y+size),fill=color) 205 draw.line((x-size,y+size,x+size,y-size),fill=color) 206 elif shape == 5: 207 # hollow diamond 208 draw.polygon((x-size,y,x,y-size,x+size,y,x,y+size,x-size,y),outline=color,fill=None) 209 elif shape == 6: 210 # hollow inv triangle 211 draw.polygon((x,y+size,x-size,y-size,x+size,y-size,x,y+size),outline=color,fill=None) 212 elif shape == 7: 213 # box cross 214 draw.rectangle((x-size,y-size,x+size,y+size),outline=color,fill=None) 215 draw.line((x-size,y-size,x+size,y+size),fill=color) 216 draw.line((x-size,y+size,x+size,y-size),fill=color) 217 elif shape == 8: 218 # star 219 draw.line((x-size,y,x+size,y),fill=color) 220 draw.line((x,y-size,x,y+size),fill=color) 221 draw.line((x-size,y-size,x+size,y+size),fill=color) 222 draw.line((x-size,y+size,x+size,y-size),fill=color) 223 elif shape == 9: 224 # diamond plus 225 draw.polygon((x-size,y,x,y-size,x+size,y,x,y+size,x-size,y),outline=color,fill=None) 226 draw.line((x-size,y,x+size,y),fill=color) 227 draw.line((x,y-size,x,y+size),fill=color) 228 elif shape == 10: 229 # circle plus 230 draw.ellipse((x-size,y-size,x+size,y+size),outline=color,fill=None) 231 draw.line((x-size,y,x+size,y),fill=color) 232 draw.line((x,y-size,x,y+size),fill=color) 233 elif shape == 11: 234 # double triangle 235 draw.polygon((x,y-size,x-size,y+0.707*size,x+size,y+0.707*size,x,y-size),outline=color,fill=None) 236 draw.polygon((x,y+size,x-size,y-0.707*size,x+size,y-0.707*size,x,y+size),outline=color,fill=None) 237 elif shape == 12: 238 # box plus 239 draw.rectangle((x-size,y-size,x+size,y+size),outline=color,fill=None) 240 draw.line((x-size,y,x+size,y),fill=color) 241 draw.line((x,y-size,x,y+size),fill=color) 242 elif shape == 13: 243 # circle cross 244 draw.ellipse((x-size,y-size,x+size,y+size),outline=color,fill=None) 245 draw.line((x-size,y-size,x+size,y+size),fill=color) 246 draw.line((x-size,y+size,x+size,y-size),fill=color) 247 elif shape == 14: 248 # box triangle 249 draw.rectangle((x-size,y-size,x+size,y+size),outline=color,fill=None) 250 draw.polygon((x,y-size,x-size,y+size,x+size,y+size,x,y-size),outline=color,fill=None) 251 elif shape == 15: 252 # solid square 253 draw.rectangle((x-size,y-size,x+size,y+size),outline=color,fill=color) 254 elif shape == 16: 255 # solid circle 256 draw.ellipse((x-size,y-size,x+size,y+size),outline=color,fill=color) 257 elif shape == 17: 258 # solid triangle 259 draw.polygon((x,y-size,x-size,y+size,x+size,y+size,x,y-size),outline=color,fill=color) 260 elif shape == 18: 261 # solid diamond 262 draw.polygon((x-size,y,x,y-size,x+size,y,x,y+size,x-size,y),outline=color,fill=color) 263 elif shape == 19: 264 # solid circle 265 draw.ellipse((x-size,y-size,x+size,y+size),outline=color,fill=color) 266 elif shape == 20: 267 # solid circle 268 draw.ellipse((x-0.707*size,y-0.707*size,x+0.707*size,y+0.707*size),outline=color,fill=color) 269 270 # elif shape == 11: 271 # # solid inv triangle 272 # draw.polygon((x,y+size,x-size,y-size,x+size,y-size,x,y+size),outline=color,fill=color) 273 # elif shape == 13: 274 # # solid right triangle 275 # draw.polygon((x-size,y-size,x+size,y,x-size,y+size,x-size,y-size),outline=color,fill=color) 276 # elif shape == 14: 277 # # hollow right triangle 278 # draw.polygon((x-size,y-size,x+size,y,x-size,y+size,x-size,y-size),outline=color,fill=None) 279 # elif shape == 15: 280 # # solid left triangle 281 # draw.polygon((x+size,y-size,x+size,y+size,x-size,y,x+size,y-size),outline=color,fill=color) 282 # elif shape == 16: 283 # # hollow left triangle 284 # draw.polygon((x+size,y-size,x+size,y+size,x-size,y,x+size,y-size),outline=color,fill=None) 285 elif type(shape) == str: 286 # render as a text label 287 if size < 6: 288 drawLabel(plot_image, (x,y), shape, size='small',align='center',rotate=False,color=color) 289 else: 290 drawLabel(plot_image, (x,y), shape, size='large',align='center',rotate=False,color=color) 291 else: 292 # fill in a pixel 293 draw.point((x,y),fill=color) 294 295 del draw
296
297 - def range(self):
298 points = self.points 299 300 points = np.array(points) 301 302 303 xmin,xmax,ymin,ymax = points[0][0],points[0][0],points[0][1],points[0][1] 304 305 for x,y in self.points: 306 #print x,y,xmin,ymin 307 xmin = np.min([xmin,x]) 308 xmax = np.max([xmax,x]) 309 ymin = np.min([ymin,y]) 310 ymax = np.max([ymax,y]) 311 #print "PointRange:", xmin,xmax,ymin,ymax 312 return xmin,xmax,ymin,ymax
313 314 315 316
317 -class Plot:
318 - def __init__(self,size=(600,600),x_range=None,y_range=None,title="No Title",ylabel="Y Axis",xlabel="X Axis",pad=True):
319 # Save plot information 320 self.size = size 321 self.title = title 322 self.xlabel = xlabel 323 self.ylabel = ylabel 324 self.x_at = None 325 self.x_labels = None 326 327 self.x_range = x_range 328 if x_range != None: 329 self.x_range = (np.min(x_range),np.max(x_range)) 330 331 self.y_range = y_range 332 if y_range != None: 333 self.y_range = (np.min(y_range),np.max(y_range)) 334 335 self.top = 60 336 self.left = 60 337 self.bottom = 60 338 self.right = 30 339 340 self.pad = pad 341 342 self.graphics = []
343 344 345 346
347 - def asImage(self):
348 w,h = self.size 349 350 fw = self.left+w+self.right 351 fh = self.top+h+self.bottom 352 353 pil = PIL.Image.new("RGB",(fw,fh),'white') 354 355 drawLabel(pil,(0.5*fw,0.5*self.top),self.title,align='center',size='huge') 356 drawLabel(pil,(5,self.top+0.5*h),self.ylabel,align='right',size='large',rotate=True) 357 drawLabel(pil,(self.left+0.5*w,fh-5),self.xlabel,align='above',size='large',rotate=False) 358 359 tmp_image = PIL.Image.new("RGB",(w,h),'white') 360 bounds = self.range() 361 for each in self.graphics: 362 each.draw(self,tmp_image,bounds) 363 364 pil.paste(tmp_image,(self.left,self.top)) 365 366 draw = PIL.ImageDraw.Draw(pil) 367 draw.rectangle((self.left,self.top,self.left+w,self.top+h),outline='black',fill=None) 368 del draw 369 370 # Draw the X axis labels 371 at,labels = self.xAxis() 372 373 # Tickmarks 374 draw = PIL.ImageDraw.Draw(pil) 375 for i in range(len(at)): 376 x = self.left+self.x(at[i],bounds) 377 y = self.top+h 378 draw.line((x,y,x,y+5),fill='black') 379 del draw 380 381 # Labels 382 for i in range(len(at)): 383 x = self.left+self.x(at[i],bounds) 384 y = self.top+h 385 drawLabel(pil, (x,y+5), labels[i], size='large',align='bellow',rotate=False,color='black') 386 387 # Draw the Y axis labels 388 at,labels = self.yAxis() 389 390 # Tickmarks 391 draw = PIL.ImageDraw.Draw(pil) 392 for i in range(len(at)): 393 x = self.left 394 y = self.top+self.y(at[i],bounds) 395 draw.line((x,y,x-5,y),fill='black') 396 del draw 397 398 # Labels 399 for i in range(len(at)): 400 x = self.left 401 y = self.top+self.y(at[i],bounds) 402 drawLabel(pil, (x-5,y), labels[i], size='large',align='left',rotate=True,color='black') 403 404 return pv.Image(pil)
405
406 - def xLabels(self,at,labels=None):
407 at = np.array(at,np.float64) 408 if labels != None: 409 assert len(at) == len(labels) 410 labels = np.array(labels,np.str) 411 else: 412 labels = np.array(at,np.str) 413 self.x_at = at 414 self.x_labels = labels
415
416 - def x(self,x,bounds):
417 minx,maxx,_,_ = bounds 418 w,_ = self.size 419 return w*(x-minx)/float(maxx-minx)
420 421
422 - def y(self,y,bounds):
423 _,_,miny,maxy = bounds 424 _,h = self.size 425 return h-h*(y-miny)/float(maxy-miny)
426
427 - def range(self):
428 xmin,xmax,ymin,ymax = -1.0,1.0,-1.0,1.0 429 if len(self.graphics) > 0: 430 xmin,xmax,ymin,ymax = self.graphics[0].range() 431 for each in self.graphics: 432 t1,t2,t3,t4 = each.range() 433 xmin = np.min([xmin,t1]) 434 xmax = np.max([xmax,t2]) 435 ymin = np.min([ymin,t3]) 436 ymax = np.max([ymax,t4]) 437 438 # Override default x_range 439 if self.x_range != None: 440 xmin,xmax = self.x_range 441 442 # Override default y_range 443 if self.y_range != None: 444 ymin,ymax = self.y_range 445 446 #print "Range:", self.x_range 447 #print "Range:", xmin,xmax,ymin,ymax 448 if self.pad: 449 rg = xmax-xmin 450 xmin -= 0.05*rg 451 xmax += 0.05*rg 452 453 rg = ymax-ymin 454 ymin -= 0.05*rg 455 ymax += 0.05*rg 456 457 return xmin,xmax,ymin,ymax
458 459
460 - def xAxis(self):
461 minx,maxx,_,_ = self.range() 462 w,_ = self.size 463 if self.x_at == None: 464 at,labels = self.autoAxis(minx,maxx,w) 465 else: 466 at,labels = self.x_at,self.x_labels 467 return at,labels
468 469
470 - def yAxis(self):
471 _,_,miny,maxy = self.range() 472 _,h = self.size 473 at,labels = self.autoAxis(miny,maxy,h) 474 return at,labels
475
476 - def autoAxis(self,low,high,size):
477 split = (0.1,0.2,0.5,1,2,5) 478 if high == low: 479 high = high+1 480 low = low -1 481 rg = high - low 482 l10 = np.log(rg)/np.log(10) 483 f10 = np.floor(l10) 484 best_at = [] 485 best_count = 500000000000000000000000 486 target_count = size/100 487 scale = 10**f10 488 low = low/scale 489 high = high/scale 490 491 for inc in split: 492 tmp = np.arange(np.floor(low),high+0.001,inc) 493 idx = (tmp >= low) & (tmp <= high) 494 tmp = tmp[idx] 495 count = len(tmp) 496 if np.abs(count-target_count) < np.abs(target_count - best_count): 497 best_count = count 498 best_at = tmp*scale 499 500 if f10 >= 1.0: 501 label_format = "%0.0f" 502 labels = [label_format%x for x in best_at] 503 else: 504 label_format = "%0." + "%d"%(-int(f10-1)) + "f" 505 labels = [label_format%x for x in best_at] 506 507 return best_at,labels
508
509 - def convertPoints(self,points):
510 try: 511 tmp = [] 512 for pt in points: 513 tmp.append((pt.X(),pt.Y())) 514 return tmp 515 except: 516 try: 517 tmp = [] 518 for pt in points: 519 #print pt 520 tmp.append((float(pt[0]),float(pt[1]))) 521 return tmp 522 except: 523 raise
524 #raise ValueError("Could not read points.") 525
526 - def label(self,point,label,**kwargs):
527 ''' render a label at multiple points''' 528 label = Label(point,label,**kwargs) 529 self.graphics.append(label)
530
531 - def points(self,points,color='black',shape=0,size=3,label=None,lty=None,width=1):
532 ''' render multiple points''' 533 if len(points) < 1: 534 return 535 points = self.convertPoints(points) 536 points = Points(points,'points',color=color,shape=shape,size=size,label=label,lty=lty,width=width) 537 self.graphics.append(points)
538
539 - def point(self,point,color='black',shape=0,size=3,label=None,lty=None,width=1):
540 ''' render a single point ''' 541 points = self.convertPoints([point]) 542 points = Points(points,'points',color=color,shape=shape,size=size,label=label,lty=lty,width=width) 543 self.graphics.append(points)
544
545 - def lines(self,points,color='black',shape=None,size=3,label=None,lty=1,width=1):
546 ''' render some lines ''' 547 if len(points) < 1: 548 return 549 points = self.convertPoints(points) 550 points = Points(points,'lines',color=color,shape=shape,size=size,label=label,lty=lty,width=width) 551 self.graphics.append(points)
552
553 - def polygon(self,points,color='black',shape=None,size=3,label=None,lty=1,width=1):
554 ''' render a closed polygon ''' 555 if len(points) < 1: 556 return 557 points = self.convertPoints(points) 558 points = Points(points,'polygon',color=color,shape=shape,size=size,label=label,lty=lty,width=width) 559 self.graphics.append(points)
560
561 - def show(self,**kwargs):
562 self.asImage().show(**kwargs)
563
564 - def asR(self,plot_pdf=os.getcwd()+"/out.pdf"):
565 ''' 566 Generate an R script that will reproduce this plot. 567 ''' 568 # Create a file object for output 569 f = StringIO.StringIO() 570 571 # Generate Configuration 572 f.write("# This is an R script that will generate a plot.\n") 573 f.write("# These first few lines are configuration options.\n") 574 f.write("filename='%s';\n"%plot_pdf) 575 f.write("plot_width=6; # width in inches\n") 576 f.write("plot_height=6; # height in inches\n") 577 f.write("title='%s'\n"%self.title) 578 f.write("xlabel='%s'\n"%self.xlabel) 579 f.write("ylabel='%s'\n"%self.ylabel) 580 f.write("xrange=c(%f,%f)\n"%self.range()[:2]) 581 f.write("yrange=c(%f,%f)\n"%self.range()[2:]) 582 f.write("\n") 583 f.write("\n") 584 f.write("\n") 585 f.write("\n") 586 587 # Create the plot 588 f.write("pdf(file=filename,width=plot_width,height=plot_height)\n") 589 f.write("\n") 590 f.write("par(mai=c(0.5,0.5,0.5,0.1)) # minimal inner margin\n") 591 f.write("par(mgp=c(1.5,0.5,0.0)) # small axes label spacing\n") 592 f.write("\n") 593 f.write("plot(xrange,yrange,type='n',main=title,xlab=xlabel,ylab=ylabel)\n") 594 f.write("# Log scale...\n") 595 f.write("# Custom axis...\n") 596 f.write("\n") 597 598 # Generate stubs for common plot additions such as labels. 599 f.write("\n") 600 f.write("# axis(1,at=c(1,3,4),labels =c(1,3,4)) # xaxis. Also add xaxt='n' to the plot command.\n") 601 f.write("# axis(2,at=c(1,3,4),labels =c(1,3,4)) # yaxis. Also add yaxt='n' to the plot command.\n") 602 f.write("# legend('topright',c('Label 1','Label 2')),fill=c('blue','green'))\n") 603 f.write("\n") 604 605 # Render the data 606 for each in self.graphics: 607 f.write("# DRAWING: %s\n"%(each.__class__)) 608 each.drawR(f) 609 f.write("\n") 610 #print each 611 612 # Finish up 613 f.write("dev.off()\n") 614 f.write("\n") 615 f.write("\n") 616 617 f.flush() 618 619 p_in,p_out = os.popen2("R --no-save") 620 p_in.write(f.getvalue()) 621 p_in.close() 622 print p_out.read() 623 p_out.close() 624 625 return f.getvalue()
626 627 628 629 import unittest 630
631 -class TestPlot(unittest.TestCase):
632
633 - def testAutoAxis(self):
634 "Plot: Auto Axis" 635 plot = Plot() 636 plot.autoAxis(0.0,10.0,600) 637 plot.autoAxis(-1.0,100.0,600) 638 plot.autoAxis(-1.0,1.0,600) 639 plot.autoAxis(-10.0,20.,600) 640 plot.autoAxis(200.0,500.,600) 641 plot.autoAxis(.210,.500,600)
642
643 - def testRangeZero(self):
644 "Plot: Range = 0" 645 plot = Plot(xrange=[0,0],yrange=[0,0]) 646 plot.asImage()
647
648 - def testNoData(self):
649 "Plot: No Data" 650 plot = Plot() 651 plot.asImage()
652 653
654 - def testDataToFormatedList(self):
655 print 656 print dataToFormatedList(range(4)) 657 print dataToFormatedList(range(5)) 658 print dataToFormatedList(range(6)) 659 print dataToFormatedList(range(7)) 660 print dataToFormatedList(range(8)) 661 print dataToFormatedList(range(9)) 662 print dataToFormatedList(range(10)) 663 print dataToFormatedList(range(15)) 664 print dataToFormatedList(range(16)) 665 print dataToFormatedList(range(17)) 666 print
667