Package topo :: Package plotting :: Module plot
[hide private]
[frames] | no frames]

Source Code for Module topo.plotting.plot

  1  """ 
  2  Plot class. 
  3   
  4  $Id: plot.py 11314 2010-07-27 17:18:52Z ceball $ 
  5  """ 
  6  __version__='$Revision: 11314 $' 
  7   
  8   
  9  import copy 
 10   
 11  from numpy.oldnumeric import zeros, ones, Float, divide 
 12   
 13  import param 
 14   
 15  from topo.base.sheetcoords import SheetCoordinateSystem,Slice 
 16   
 17  from bitmap import HSVBitmap, RGBBitmap, Bitmap 
 18   
 19   
 20  ### JCALERT! 
 21  ### - Re-write the test file, taking the new changes into account. 
 22  ### - I have to change the order: situate, plot_bb and (normalize) 
 23  ### - There should be a way to associate the density explicitly 
 24  ###   with the sheet_views, because it must match all SheetViews 
 25  ###   in that dictionary.  Maybe as a tuple? 
 26  ### - Fix the plot name handling along with the view_info sheetview attribute 
 27  ### - Get rid of release_sheetviews. 
 28   
 29   
30 -class Plot(param.Parameterized):
31 """ 32 Simple Plot object constructed from a specified PIL image. 33 """ 34 35 staleness_warning=param.Number(default=10,bounds=(0,None),doc=""" 36 Time length allowed between bitmaps making up a single plot before warning. 37 38 If the difference between the SheetView with the earliest 39 timestamp and the one with the latest timestamp is larger 40 than this parameter's value, produce a warning. 41 """) 42
43 - def __init__(self,image=None,**params):
44 super(Plot,self).__init__(**params) 45 self._orig_bitmap = Bitmap(image) 46 self.bitmap = self._orig_bitmap # Possibly scaled copy (at first identical) 47 self.scale_factor=1.0 48 self.plot_src_name = '' 49 self.precedence = 0.0 50 self.row_precedence = 0.5 51 # If False, this plot should be left in its native size 52 # pixel-for-pixel, (e.g. for a color key or similar static 53 # image), rather than being resized as necessary. 54 self.resize=False 55 56 # Time at which the bitmaps were created 57 self.timestamp = -1
58 59
60 - def rescale(self,scale_factor):
61 """ 62 Change the size of this image by the specified numerical factor. 63 64 The original image is kept as-is in _orig_bitmap; the scaled 65 image is stored in bitmap. The scale_factor argument is 66 taken as relative to the current scaling of the bitmap. For 67 instance, calling scale(1.5) followed by scale(2.0) will 68 yield a final scale of 3.0, not 2.0. 69 """ 70 self.scale_factor *= scale_factor 71 72 if (self._orig_bitmap): 73 self.bitmap = copy.copy(self._orig_bitmap) 74 self.bitmap.image = self._orig_bitmap.zoom(self.scale_factor)
75 76
77 - def set_scale(self,scale_factor):
78 """ 79 Specify the numerical value of the scaling factor for this image. 80 81 The original image is kept as-is in _orig_bitmap; the scaled 82 image is stored in bitmap. The scale_factor argument is 83 taken as relative to the original size of the bitmap. For 84 instance, calling scale(1.5) followed by scale(2.0) will 85 yield a final scale of 2.0, not 3.0. 86 """ 87 self.scale_factor = scale_factor 88 89 if (self._orig_bitmap): 90 self.bitmap = copy.copy(self._orig_bitmap) 91 self.bitmap.image = self._orig_bitmap.zoom(self.scale_factor)
92
93 - def label(self):
94 """Return a label for this plot.""" 95 return self.plot_src_name + '\n' + self.name
96 97 98
99 -def _sane_plot_data(channels,sheet_views):
100 # CEBALERT: was sf.net tracker item 1860837 101 # (Avoid plotting only hue+confidence for a weights plot.) 102 s_chan = channels.get('Strength') 103 if s_chan is not None and len(s_chan)>0 and s_chan[0]=='Weights': 104 return channels['Strength'] in sheet_views 105 else: 106 return True
107 108 # JABALERT: How can we handle joint normalization, where a set of 109 # plots (e.g. a CFProjectionPlotGroup, or the jointly normalized 110 # subset of a ConnectionFields plot) is all scaled by the same amount, 111 # so that relative strengths can be determined? Maybe we can have 112 # make_template_plot and the various TemplatePlot types accept a 113 # parameter 'range_only' that makes them simply calculate a pair 114 # (min,max) with the values to use for scaling, and then the caller 115 # (e.g. CFProjectionPlotGroup._create_plots) would run through 116 # everything twice, first to get the ranges, and then the next time it 117 # would supply an explicit range for scaling (overriding the default 118 # single-plot normalization)? See the commented-out code for 119 # value_range below for a start. I *think* that would work, but maybe 120 # there is some simpler way?
121 -def make_template_plot(channels,sheet_views,density=None, 122 plot_bounding_box=None,normalize='None', 123 name='None',range_=False):
124 """ 125 Factory function for constructing a Plot object whose type is not yet known. 126 127 Typically, a TemplatePlot will be constructed through this call, because 128 it selects the appropriate type automatically, rather than calling 129 one of the Plot subclasses automatically. See TemplatePlot.__init__ for 130 a description of the arguments. 131 """ 132 if _sane_plot_data(channels,sheet_views): 133 plot_types=[SHCPlot,RGBPlot,PalettePlot] 134 for pt in plot_types: 135 plot = pt(channels,sheet_views,density,plot_bounding_box,normalize, 136 name=name,range_=range_) 137 if plot.bitmap is not None or range_ is None: 138 # range_ is None means we're calculating the range 139 return plot 140 141 param.Parameterized(name="make_template_plot").verbose('No',name,'plot constructed for this Sheet') 142 return None
143 144 145
146 -class TemplatePlot(Plot):
147 """ 148 A bitmap-based plot as specified by a plot template (or plot channels). 149 """ 150 151 # Not sure why, but this has to be a Parameter to avoid spurious complaints 152 warn_time=param.Number(-2,precedence=-1,doc="Time last warned about stale plots") 153 154
155 - def __init__(self,channels,sheet_views,density, 156 plot_bounding_box,normalize, 157 range_=False,**params):
158 """ 159 Build a plot out of a set of SheetViews as determined by a plot_template. 160 161 channels is a plot_template, i.e. a dictionary with keys 162 (i.e. 'Strength','Hue','Confidence' ...). Each key typically 163 has a string value naming specifies a SheetView in 164 sheet_views, though specific channels may contain other 165 types of information as required by specific Plot subclasses. 166 channels that are not used by a particular Plot subclass will 167 silently be ignored. 168 169 sheet_views is a dictionary of SheetViews, generally (but 170 not necessarily) belonging to a Sheet object. 171 172 density is the density of the Sheet whose sheet_views was 173 passed. 174 175 plot_bounding_box is the outer bounding_box of the plot to 176 apply if specified. If not, the bounds of 177 the smallest SheetView are used. 178 179 normalize specifies how the Plot should be normalized: any 180 value of normalize other than 'None' will result in normalization 181 according to the value of the range argument: 182 183 range=(A,B) - scale plot so that A is 0 and B is 1 184 185 range=False - scale plot so that min(plot) is 0 and 186 max(plot) is 1 (i.e. fill the maximim 187 dynamic range) 188 189 range=None - calculate value_range only 190 191 192 name (which is inherited from Parameterized) specifies the name 193 to use for this plot. 194 """ 195 super(TemplatePlot,self).__init__(**params) 196 # for a template plot, resize is True by default 197 self.resize=True 198 self.bitmap = None 199 200 201 self.channels = channels 202 self.view_dict = copy.copy(sheet_views) 203 # bounds of the situated plotting area 204 self.plot_bounding_box = plot_bounding_box 205 206 207 ### JCALERT ! The problem of displaying the right plot name is still reviewed 208 ### at the moment we have the plot_src_name and name attribute that are used for the label. 209 ### generally the name is set to the plot_template name, except for connection 210 # set the name of the sheet that provides the SheetViews 211 # combined with the self.name parameter when creating the plot (which is generally 212 # the name of the plot_template), it provides the necessary information for displaying plot label 213 self._set_plot_src_name()
214 215 216 # # Eventually: support other type of plots (e.g vector fields...) using 217 # # something like: 218 # def annotated_bitmap(self): 219 # enable other construction.... 220 221
222 - def _get_matrix(self,key):
223 """ 224 Retrieve the matrix view associated with a given key, if any. 225 226 If the key is found in self.channels and the corresponding 227 sheetview is found in self.view_dict, the view's matrix is 228 returned; otherwise None is returned (with no error). 229 """ 230 sheet_view_key = self.channels.get(key,None) 231 sv = self.view_dict.get(sheet_view_key, None) 232 if sv == None: 233 matrix = None 234 else: 235 view = sv.view() 236 matrix = view[0] 237 238 # Calculate timestamp for this plot 239 timestamp = sv.timestamp 240 if timestamp >=0: 241 if self.timestamp < 0: 242 self.timestamp = timestamp 243 elif abs(timestamp - self.timestamp) > self.staleness_warning: 244 if TemplatePlot.warn_time != min(timestamp, self.timestamp): 245 self.warning("Combining SheetViews from different times (%s,%s) for plot %s; see staleness_warning" % 246 (timestamp, self.timestamp,self.name)) 247 TemplatePlot.warn_time = min(timestamp, self.timestamp) 248 return matrix
249 250
251 - def _set_plot_src_name(self):
252 """ Set the Plot plot_src_name. Called when Plot is created""" 253 for key in self.channels: 254 sheet_view_key = self.channels.get(key,None) 255 sv = self.view_dict.get(sheet_view_key, None) 256 if sv != None: 257 self.plot_src_name = sv.src_name 258 self.precedence = sv.precedence 259 self.row_precedence = sv.row_precedence 260 if hasattr(sv,'proj_src_name'): 261 self.proj_src_name=sv.proj_src_name
262 263 264 ### JCALERT: This could be inserted in the code of get_matrix
265 - def _get_shape_and_box(self):
266 """ 267 Sub-function used by plot: get the matrix shape and the bounding box 268 of the SheetViews that constitue the TemplatePlot. 269 """ 270 for name in self.channels.values(): 271 sv = self.view_dict.get(name,None) 272 if sv != None: 273 shape = sv.view()[0].shape 274 box = sv.view()[1] 275 276 return shape,box
277 278 279 # CEBALERT: needs simplification! (To begin work on joint 280 # normalization, I didn't want to interfere with the existing 281 # normalization calculations.) Also need to update this 282 # docstring. 283 # 284 # range=None - calculate value_range; don't scale a 285 # range=(A,B) - scale a so that A is 0 and B is 1 286 # range=False - scale a so that min(array) is 0 and max(array) is 1
287 - def _normalize(self,a,range_):
288 """ 289 Normalize an array s to be in the range 0 to 1.0. 290 For an array of identical elements, returns an array of ones 291 if the elements are greater than zero, and zeros if the 292 elements are less than or equal to zero. 293 """ 294 if range_: # i.e. not False, not None (expecting a tuple) 295 range_min = float(range_[0]) 296 range_max = float(range_[1]) 297 298 if range_min==range_max: 299 if range_min>0: 300 resu = ones(a.shape) 301 else: 302 resu = zeros(a.shape) 303 else: 304 a_offset = a - range_min 305 resu = a_offset/range_max 306 307 return resu 308 else: 309 if range_ is None: 310 if not hasattr(self,'value_range'): 311 self.value_range=(a.min(),a.max()) 312 else: 313 # If normalizing multiple matrices, take the largest values 314 self.value_range=(min(self.value_range[0],a.min()), 315 max(self.value_range[1],a.max())) 316 return None # (indicate that array was not scaled) 317 else: # i.e. range_ is False 318 a_offset = a-a.min() 319 max_a_offset = a_offset.max() 320 321 if max_a_offset>0: 322 a = divide(a_offset,float(max_a_offset)) 323 else: 324 if min(a.ravel())<=0: 325 a=zeros(a.shape,Float) 326 else: 327 a=ones(a.shape,Float) 328 return a
329 330 331 ### JC: maybe density can become an attribute of the TemplatePlot?
332 - def _re_bound(self,plot_bounding_box,mat,box,density):
333 334 # CEBHACKALERT: for Julien... 335 # If plot_bounding_box is that of a Sheet, it will already have been 336 # setup so that the density in the x direction and the density in the 337 # y direction are equal. 338 # If plot_bounding_box comes from elsewhere (i.e. you create it from 339 # arbitrary bounds), it might need to be adjusted to ensure the density 340 # in both directions is the same (see Sheet.__init__()). I don't know where 341 # you want to do that; presumably the code should be common to Sheet and 342 # where it's used in the plotting? 343 # 344 # It's possible we can move some of the functionality 345 # into SheetCoordinateSystem. 346 if plot_bounding_box.containsbb_exclusive(box): 347 ct = SheetCoordinateSystem(plot_bounding_box,density,density) 348 new_mat = zeros(ct.shape,Float) 349 r1,r2,c1,c2 = Slice(box,ct) 350 new_mat[r1:r2,c1:c2] = mat 351 else: 352 scs = SheetCoordinateSystem(box,density,density) 353 s=Slice(plot_bounding_box,scs) 354 s.crop_to_sheet(scs) 355 new_mat = s.submatrix(mat) 356 357 return new_mat
358 359 360 361 362
363 -class SHCPlot(TemplatePlot):
364 """ 365 Bitmap plot based on Strength, Hue, and Confidence matrices. 366 367 Constructs an HSV (hue, saturation, and value) plot by choosing 368 the appropriate matrix for each channel. 369 """ 370
371 - def __init__(self,channels,sheet_views,density, 372 plot_bounding_box,normalize, 373 range_=False,**params):
374 super(SHCPlot,self).__init__(channels,sheet_views,density, 375 plot_bounding_box,normalize,**params) 376 377 # catching the empty plot exception 378 s_mat = self._get_matrix('Strength') 379 h_mat = self._get_matrix('Hue') 380 c_mat = self._get_matrix('Confidence') 381 382 # If it is an empty plot: self.bitmap=None 383 if (s_mat==None and c_mat==None and h_mat==None): 384 self.debug('Empty plot.') 385 386 # Otherwise, we construct self.bitmap according to what is specified by the channels. 387 else: 388 389 shape,box = self._get_shape_and_box() 390 391 hue,sat,val = self.__make_hsv_matrices((s_mat,h_mat,c_mat),shape,normalize,range_) 392 393 if range_ is None: 394 return ############################## 395 396 397 if self.plot_bounding_box == None: 398 self.plot_bounding_box = box 399 400 hue = self._re_bound(self.plot_bounding_box,hue,box,density) 401 sat = self._re_bound(self.plot_bounding_box,sat,box,density) 402 val = self._re_bound(self.plot_bounding_box,val,box,density) 403 404 self.bitmap = HSVBitmap(hue,sat,val) 405 406 self._orig_bitmap=self.bitmap
407 408
409 - def __make_hsv_matrices(self,hsc_matrices,shape,normalize,range_=False):
410 """ 411 Sub-function of plot() that return the h,s,v matrices corresponding 412 to the current matrices in sliced_matrices_dict. The shape of the matrices 413 in the dict is passed, as well as the normalize boolean parameter. 414 The result specified a bitmap in hsv coordinate. 415 416 Applies normalizing and cropping if required. 417 """ 418 zero=zeros(shape,Float) 419 one=ones(shape,Float) 420 421 s,h,c = hsc_matrices 422 # Determine appropriate defaults for each matrix 423 if s is None: s=one # Treat as full strength by default 424 if c is None: c=one # Treat as full confidence by default 425 if h is None: # No color, gray-scale plot. 426 h=zero 427 c=zero 428 429 # If normalizing, offset the matrix so that the minimum 430 # value is 0.0 and then scale to make the maximum 1.0 431 if normalize!='None': 432 s=self._normalize(s,range_=range_) 433 # CEBALERT: I meant False, right? 434 c=self._normalize(c,range_=False) 435 436 437 # This translation from SHC to HSV is valid only for black backgrounds; 438 # it will need to be extended also to support white backgrounds. 439 hue,sat,val=h,c,s 440 return (hue,sat,val)
441 442 443 444 445
446 -class RGBPlot(TemplatePlot):
447 """ 448 Bitmap plot based on Red, Green, and Blue matrices. 449 450 Construct an RGB (red, green, and blue) plot from the Red, Green, 451 and Blue channels. 452 """
453 - def __init__(self,channels,sheet_views,density, 454 plot_bounding_box,normalize, 455 range_=False,**params):
456 457 super(RGBPlot,self).__init__(channels,sheet_views,density, 458 plot_bounding_box,normalize,**params) 459 460 461 # catching the empty plot exception 462 r_mat = self._get_matrix('Red') 463 g_mat = self._get_matrix('Green') 464 b_mat = self._get_matrix('Blue') 465 466 # If it is an empty plot: self.bitmap=None 467 if (r_mat==None and g_mat==None and b_mat==None): 468 self.debug('Empty plot.') 469 # Otherwise, we construct self.bitmap according to what is specified by the channels. 470 else: 471 472 shape,box = self._get_shape_and_box() 473 474 red,green,blue = self.__make_rgb_matrices((r_mat,g_mat,b_mat),shape, 475 normalize,range_=range_) 476 477 if range_ is None: 478 return ############################ 479 480 if self.plot_bounding_box == None: 481 self.plot_bounding_box = box 482 483 red = self._re_bound(self.plot_bounding_box,red,box,density) 484 green = self._re_bound(self.plot_bounding_box,green,box,density) 485 blue = self._re_bound(self.plot_bounding_box,blue,box,density) 486 487 self.bitmap = RGBBitmap(red,green,blue) 488 489 self._orig_bitmap=self.bitmap
490
491 - def __make_rgb_matrices(self, rgb_matrices,shape,normalize,range_=False):
492 """ 493 Sub-function of plot() that return the h,s,v matrices 494 corresponding to the current matrices in 495 sliced_matrices_dict. The shape of the matrices in the dict is 496 passed, as well as the normalize boolean parameter. The 497 result specified a bitmap in hsv coordinate. 498 499 Applies normalizing and cropping if required. 500 """ 501 zero=zeros(shape,Float) 502 one=ones(shape,Float) 503 504 r,g,b = rgb_matrices 505 # Determine appropriate defaults for each matrix 506 if r is None: r=zero 507 if g is None: g=zero 508 if b is None: b=zero 509 510 # CEBALERT: have I checked this works? 511 if normalize!='None': 512 r = self._normalize(r,range_=range_) 513 g = self._normalize(g,range_=range_) 514 b = self._normalize(b,range_=range_) 515 516 return (r,g,b)
517 518 519 520 521
522 -class PalettePlot(TemplatePlot):
523 """ 524 Bitmap plot based on a Strength matrix, with optional colorization. 525 526 Not yet implemented. 527 528 When implemented, construct an RGB plot from a Strength channel, 529 optionally colorized using a specified Palette. 530 """ 531
532 - def __init__(self,channels,sheet_views,density, 533 plot_bounding_box,normalize,**params):
534 535 super(PalettePlot,self).__init__(channels,sheet_views,density, 536 plot_bounding_box,normalize,**params)
537 538 ### JABHACKALERT: To implement the class: If Strength is present, 539 ### ask for Palette if it's there, and make a PaletteBitmap. 540