Package qubx :: Module notebook
[hide private]
[frames] | no frames]

Source Code for Module qubx.notebook

  1  """Base classes and registry for notebook figure export. 
  2   
  3  Copyright 2008-2012 Research Foundation State University of New York  
  4  This file is part of QUB Express.                                           
  5   
  6  QUB Express is free software; you can redistribute it and/or modify           
  7  it under the terms of the GNU General Public License as published by  
  8  the Free Software Foundation, either version 3 of the License, or     
  9  (at your option) any later version.                                   
 10   
 11  QUB Express is distributed in the hope that it will be useful,                
 12  but WITHOUT ANY WARRANTY; without even the implied warranty of        
 13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         
 14  GNU General Public License for more details.                          
 15   
 16  You should have received a copy of the GNU General Public License,    
 17  named LICENSE.txt, in the QUB Express program directory.  If not, see         
 18  <http://www.gnu.org/licenses/>.                                       
 19   
 20  """ 
 21   
 22  from cStringIO import StringIO 
 23  import os 
 24  import sys 
 25  import traceback 
 26  import qubx.accept 
 27  import qubx.pyenv 
 28  import qubx.settings 
 29  import qubx.tree 
 30  from qubx.util_types import Anon, WeakEvent, Reffer 
 31   
32 -class NbItem(Anon):
33 """The simplest type of notebook item; this class has the minimal properties and methods to implement the notebook protocol: 34 35 mnu_caption: text for the notebook menu, e.g. "Chart" 36 global_name: How to find this item from the global namespace, e.g. QubX.AmpHist.plots[0].nbChart 37 (for script recording) 38 to_txt: function() -> string, returns plain-text representation of this item 39 40 Subclasses may present additional information, e.g. pictures or data series, but should 41 also provide to_txt for compatibility with console output. 42 """
43 - def __init__(self, mnu_caption, global_name, to_txt, *args, **kw):
44 Anon.__init__(self, mnu_caption=mnu_caption, global_name=global_name, to_txt=to_txt, *args, **kw)
45 - def __str__(self):
46 return self.to_txt()
47
48 -class NbItems(NbItem):
49 """A bundle of related notebook items; e.g. "All Charts". Each item in items is an instance of NbItem or a subclass. 50 51 >>> if isinstance(myNbItem, NbItems): 52 ... for item in myNbItem: 53 ... print item 54 55 """
56 - def __init__(self, mnu_caption, global_name, *items):
57 NbItem.__init__(self, mnu_caption, global_name, self.__to_txt, items=list(items))
58 - def iteritems(self):
59 for item in self.items: 60 if item: 61 yield item
62 - def __iter__(self):
63 return self.iteritems()
64 - def __to_txt(self):
65 buf = StringIO() 66 for item in self: 67 buf.write(item.to_txt()) 68 return buf.getvalue()
69
70 -class NbText(NbItem):
71 """A simple notebook item with static text."""
72 - def __init__(self, msg):
73 NbItem.__init__(self, "Text", "", lambda: msg)
74
75 -class NbDynText(NbItem):
76 """A simple notebook item with text that's recomputed by the get_msg function whenever it's needed."""
77 - def __init__(self, mnu_caption, global_name, get_msg):
78 NbItem.__init__(self, mnu_caption, global_name, get_msg)
79
80 -class NbTable(NbItem):
81 """A notebook item with tabular data. Subclasses NbChart and NbTrace define series using this underlying table of data. 82 83 get_caption: function() -> str, returns caption for the table 84 get_shape: function() -> (rows, columns), returns dimensions 85 get_headers: function() -> list of str, returns column headers 86 get_row: function(r) -> list, returns a row of data 87 get_col: function(c) -> list, returns a column of data 88 (When creating an NbTable, provide either get_row or get_col and the other will be auto-generated; 89 when using an NbTable, both get_row and get_col are available.) 90 get_col_format: function(c) -> (function(val) -> str), returns a function which formats values from column c as strings (by default, the str function) 91 get_type: function() -> type; returns e.g. float or str 92 """
93 - def __init__(self, mnu_caption, global_name, get_caption, get_shape, get_headers, get_row=None, get_col=None, get_col_format=None, get_type=None, 94 **kw):
95 the_get_row = get_row or self.__get_row 96 the_get_col = get_col or self.__get_col 97 the_get_col_format = get_col_format or self.__get_col_format 98 the_get_type = get_type or self.__get_type 99 NbItem.__init__(self, mnu_caption=mnu_caption, global_name=global_name, 100 get_caption=get_caption, to_txt=self.__to_txt, get_shape=get_shape, get_headers=get_headers, 101 get_row=the_get_row, get_col=the_get_col, get_col_format=the_get_col_format, 102 get_type=the_get_type, **kw)
103 - def __get_type(self):
104 return str
105 - def __get_col_format(self, c):
106 return str
107 - def __get_col(self, c):
108 Nr, Nc = self.get_shape() 109 return [self.get_row(r)[c] for r in xrange(Nr)]
110 - def __get_row(self, r):
111 Nr, Nc = self.get_shape() 112 return [(len(col) > r) and col[r] or 0.0 for col in [self.get_col(c) for c in xrange(Nc)]]
113 - def __to_txt(self):
114 buf = StringIO() 115 buf.write('\t'.join(self.get_headers())) 116 buf.write('\n') 117 Nr, Nc = self.get_shape() 118 col_format = [self.get_col_format(c) for c in xrange(Nc)] 119 if self.__get_row != self.get_row: # rows provided directly: 120 for r in xrange(Nr): 121 buf.write('\t'.join(col_format[c](x) for c,x in enumerate(self.get_row(r)))) 122 buf.write('\n') 123 else: 124 cols = [self.get_col(c) for c in xrange(Nc)] 125 for r in xrange(Nr): 126 buf.write('\t'.join((len(cols[c]) > r) and col_format[c](cols[c][r]) or "0.0" for c in xrange(Nc))) 127 buf.write('\n') 128 return buf.getvalue()
129
130 -def NbSubTable(name='SubTable', fields=[]):
131 "Returns a function(qubx.table.Table), which returns an NbTable with only the specified columns." 132 def make_nb(table): 133 return qubx.notebook.NbTable(name, table.global_name and ('%s.notebook[%s]'%(table.global_name, repr(name))) or '', 134 get_caption=table.nb_get_caption, 135 get_shape=lambda: (table.nb_get_shape()[0], len([f for f in fields if f in table.fields])), 136 get_headers=lambda: [f for f in fields if f in table.fields], 137 get_row=lambda r: [table[r,f] for f in fields if f in table.fields], 138 get_col_format=lambda c: table.nb_get_col_format(table.fields.index([f for f in fields if f in table.fields][c])), 139 get_type=lambda: table.nb_get_type([f for f in fields if f in table.fields]))
140 make_nb.name = name 141 return make_nb 142
143 -class NbStaticTable(NbTable):
144 """A notebook item representing a specific array."""
145 - def __init__(self, arr, caption="", header="", format=str, as_column=True):
146 kw = {} 147 if as_column: 148 rows, cols = len(arr), 1 149 kw["get_col"] = lambda i: arr 150 else: 151 rows, cols = 1, len(arr) 152 kw["get_row"] = lambda i: arr 153 fmt = qubx.accept.acceptFormat(format) 154 kw["get_col_format"] = lambda i: fmt 155 if len(arr) and hasattr(arr[0], "replace"): 156 kw["get_type"] = lambda: str 157 else: 158 kw["get_type"] = lambda: float 159 NbTable.__init__(self, "Table", "", lambda: caption, lambda: (rows, cols), lambda: [header]*cols, **kw)
160
161 -class NbStaticArray(NbTable):
162 """A notebook item representing a specific 1d or 2d numpy.ndarray."""
163 - def __init__(self, arr, caption="", headers=[], format=str):
164 kw = {} 165 if len(arr.shape) == 2: 166 rows, cols = arr.shape 167 kw["get_col"] = lambda i: arr[:,i].flatten() 168 kw["get_row"] = lambda i: arr[i,:].flatten() 169 else: 170 rows, cols = 1, len(arr) 171 kw["get_col"] = lambda i: arr 172 fmt = qubx.accept.acceptFormat(format) 173 kw["get_col_format"] = lambda i: fmt 174 kw["get_type"] = lambda: float 175 hdrs = headers + (cols - len(headers)) * [""] 176 NbTable.__init__(self, "Table", "", lambda: caption, lambda: (rows, cols), lambda: hdrs, **kw)
177 178 179 180 DOTS = 0 181 LINES = 1 182 HISTOGRAM = 2 183
184 -def c2h(c):
185 h = '%x'%c 186 if len(h) == 1: 187 return '0%s'%h 188 return h
189 -def rgb_to_hex(rgb):
190 return '#%s' % ''.join(c2h(255*x) for x in rgb[:3])
191
192 -class NbChartSeries(Anon):
193 """Properties of one series in a chart item: 194 195 xcol: index of the X column 196 ycol: index of the Y column 197 first_row: this series may start on a row other than 0 198 nrow: and consist of fewer rows than the entire table 199 ser_type: one of DOTS, LINES, or HISTOGRAM = (0, 1, 2) 200 color_rgb: (r, g, b) with each coordinate between 0.0 and 1.0 201 weight: thickness of line, between 0.0 and 1.0 202 """
203 - def __init__(self, xcol, ycol, first_row=0, nrow=-1, ser_type=DOTS, color_rgb=(0,0,0), weight=1.0):
204 Anon.__init__(self, xcol=xcol, ycol=ycol, first_row=first_row, nrow=nrow, 205 ser_type=ser_type, color_rgb=color_rgb, color_hex=rgb_to_hex(color_rgb), weight=weight)
206
207 -class NbChart(NbTable):
208 """Notebook item representing a 2D plot. In addition to the data defined by its L{NbTable} properties, it defines 209 L{NbChartSeries} to lay out dots, lines and histograms using the table data. 210 211 get_xlabel: function() -> string, caption for the x axis 212 get_ylabel: function() -> string, caption for the y axis 213 get_series: function() -> list of L{NbChartSeries} 214 get_colors: function() -> list of (r,g,b) (or None), optionally defining a palette 215 get_color_indices: function -> list of int (or None), optional colors for the first series 216 """
217 - def __init__(self, mnu_caption, global_name, get_caption, get_shape, get_headers, get_xlabel=lambda: "", get_ylabel=lambda: "", 218 get_series=lambda: [], get_colors=lambda:None, get_color_indices=lambda:None, *args, **kw):
219 kw['get_xlabel'] = get_xlabel 220 kw['get_ylabel'] = get_ylabel 221 kw['get_series'] = get_series 222 kw['get_colors'] = get_colors 223 kw['get_color_indices'] = get_color_indices 224 NbTable.__init__(self, mnu_caption, global_name, get_caption, get_shape, get_headers, *args, **kw)
225
226 -class NbTrace(NbChart):
227 """Notebook item representing sampled data from one or more signals. 228 229 get_trace_series: function() -> list of L{NbChartSeries} 230 """ 231
232 - def __init__(self, mnu_caption, global_name, get_caption, get_shape, get_headers, get_trace_series=lambda: [], *args, **kw):
233 kw['get_trace_series'] = get_trace_series 234 NbChart.__init__(self, mnu_caption, global_name, get_caption, get_shape, get_headers, *args, **kw)
235 236 237 ### NbPicture in qubx.notebookGTK 238 239
240 -class NbTarget(object): # inheritance optional; just implement the methods
241 """Base class for objects which receive notebook items. Multiple NbTargets can be registered by name with the NbController qubx.notebook.Notebook. 242 Targets need not inherit from this class, but must implement these properties and methods: 243 244 supports: list of subclasses of NbItem which this target can handle, e.g. [NbText, NbDynText, NbTable] 245 nb_init(): called when the target is (re-) activated 246 nb_send(item): acts on the L{NbItem} 247 """
248 - def __init__(self):
249 self.supports = [] # e.g. [NbText, NbDynText, NbTable, NbChart, NbTrace, NbPicture]
250 - def nb_init(self):
251 pass
252 - def nb_send(self, item):
253 pass
254
255 -class NbTargetRec(Anon):
256 """Info about one registered notebook target: 257 258 target_id: short string to identify target in code 259 caption: user-visible name of target 260 menu_caption: shortened user-visible name, for menus 261 controls: gtk.Widget or None; additional gui elements 262 target: L{NbTarget} 263 """ 264 pass # target_id, caption, menu_caption, controls, target
265
266 -class NbAutoRec(Anon):
267 """Info about one automatic notebook item: 268 269 auto_id: string to programmatically identify this item and the occasion for automatically generating it 270 mnu_caption: user-visible description 271 active: False if this item is disabled (un-checked by user) 272 """ 273 pass # auto_id, mnu_caption, active
274
275 -class NbController(object):
276 """The global instance of this type, qubx.notebook.Notebook, is created by qubx.notebook.Init(), 277 and keeps registries of the various output methods (L{NbTarget}s) and automatically-triggered L{NbItem}s. 278 279 @ivar auto: dict(auto_id -> L{NbAutoRec}) 280 @ivar targets: list of L{NbTargetRec} 281 @ivar target_ix: index of user's choice in targets 282 @ivar target_id: chosen target's target_id 283 @ivar target: chosen L{NbTarget} 284 @ivar OnAddAuto: L{WeakEvent}(index, L{NbAutoRec}) 285 @ivar OnChangeAuto: L{WeakEvent}(index, L{NbAutoRec}) e.g. the user has toggled "active" 286 @ivar OnAddTarget: L{WeakEvent}(index) 287 @ivar OnRemovingTarget: L{WeakEvent}(index) 288 @ivar OnChangeTargetIx: L{WeakEvent}(index) 289 """
290 - def __init__(self):
291 self.__ref = Reffer() 292 self.auto = {} # auto_id : NbAutoRec 293 self.targets = [] # NbTargetRec 294 self.__target_ix = -1 295 self.OnAddAuto = WeakEvent() 296 self.OnChangeAuto = WeakEvent() 297 self.OnAddTarget = WeakEvent() 298 self.OnRemovingTarget = WeakEvent() 299 self.OnChangeTargetIx = WeakEvent() 300 self.props = qubx.tree.Node() # defer until targets are registered 301 self.settings_auto = qubx.settings.SettingsMgr['qubx.notebook.NbController.auto'] 302 self.settings_auto.OnSet = self.__ref(self.__onSetPropsAuto) 303 self.props_auto = self.settings_auto.active
304 - def init_settings(self):
305 self.settings = qubx.settings.SettingsMgr['qubx.notebook.NbController'] 306 have_target = not self.settings.active.find('target_id').isNull 307 self.settings.OnSet = self.__ref(self.__onSetProps) 308 self.__onSetProps(self.settings, self.settings.active) 309 self.settings.active = self.props 310 return have_target
311 - def set_target_ix(self, x):
312 if x == self.__target_ix: 313 return 314 self.__target_ix = x 315 self.props['target_id'].data = self.targets[x].target_id 316 self.__finish_set_target_ix()
317 - def __finish_set_target_ix(self):
318 if not qubx.pyenv.env.globals['QUBX_READY']: 319 qubx.pyenv.env.call_later(self.__finish_set_target_ix) 320 return 321 self.targets[self.__target_ix].target.nb_init() 322 self.OnChangeTargetIx(self.__target_ix)
323 target_ix = property(lambda self: self.__target_ix, lambda self, x: self.set_target_ix(x))
324 - def get_target_id(self):
325 if self.__target_ix < 0: 326 return "" 327 return self.targets[self.__target_ix].target_id
328 - def set_target_id(self, x):
329 if (self.__target_ix >= 0) and (x == self.targets[self.__target_ix].target_id): 330 return 331 for i, t in enumerate(self.targets): 332 if t.target_id == x: 333 self.set_target_ix(i) 334 break
335 target_id = property(lambda self: self.get_target_id(), lambda self, x: self.set_target_id(x)) 336 target = property(lambda self: self.targets[self.__target_ix])
337 - def list_target_ids(self):
338 """Returns all registered target_id values.""" 339 return [target.target_id for target in self.targets]
340 - def __onSetProps(self, settings, updates):
341 if updates.find('target_id').data: 342 self.target_id = str(updates['target_id'].data) 343 self.props['target_id'].data = self.target_id
344 - def __onSetPropsAuto(self, settings, updates):
345 for auto in qubx.tree.children(updates): 346 nm = auto.name 347 if nm and (nm in self.auto) and auto.data: 348 active = bool(auto.data[0]) 349 if active != self.auto[nm].active: 350 self.auto[nm].active = active 351 self.props_auto[nm].data = active 352 self.OnChangeAuto(sorted(self.auto.keys()).index(nm), self.auto[nm])
353 - def register_target(self, target, target_id, caption, menu_caption=None, controls=None):
354 the_menu_caption = menu_caption 355 if the_menu_caption is None: 356 the_menu_caption = caption 357 ix = -1 358 for i,t in enumerate(self.targets): 359 if t.target_id == target_id: 360 ix = i 361 break 362 if ix >= 0: 363 self.OnRemovingTarget(ix) 364 del self.targets[ix] 365 ix = len(self.targets) 366 self.targets.append(NbTargetRec(target=target, target_id=target_id, caption=caption, 367 menu_caption=the_menu_caption, controls=controls)) 368 self.OnAddTarget(ix) 369 if ix == 0: 370 self.target_ix = ix
371 - def register_auto(self, auto_id, mnu_caption, default=False):
372 """Declares that automatic items will be generated with this auto_id and caption. With default=False, the user will have to enable it in the Notebook tab.""" 373 preexist = auto_id in self.auto 374 prop = self.props_auto[auto_id] 375 if prop.data: 376 active = bool(prop.data[0]) 377 else: 378 active = default 379 prop.data = active 380 self.auto[auto_id] = NbAutoRec(auto_id=auto_id, mnu_caption=mnu_caption, active=active) 381 ix = sorted(self.auto.keys()).index(auto_id) 382 if preexist: 383 self.OnChangeAuto(ix, self.auto[auto_id]) 384 else: 385 self.OnAddAuto(ix, self.auto[auto_id])
386 - def set_auto_active(self, auto_id, active):
387 self.auto[auto_id].active = active 388 self.props_auto[auto_id].data = int(active) 389 self.OnChangeAuto(sorted(self.auto.keys()).index(auto_id), self.auto[auto_id])
390 - def send(self, item, target_ix=None, auto_id=None, target_id=None):
391 """Writes an L{NbItem} to the chosen L{NbTarget}. 392 393 @param target_ix: index of target in targets, or None to use the user's choice 394 @param target_id: if (target_ix is None), picks a target by its target_id 395 @param auto_id: for items generated automatically rather than by explicit user or script request; will check if auto item is active before emitting 396 """ 397 if auto_id and (auto_id in self.auto) and not self.auto[auto_id].active: 398 return 399 if not qubx.pyenv.env.globals['QUBX_READY']: 400 if qubx.pyenv.env.globals['DEBUG']: 401 print 'dropped early notebook item:' 402 print item.to_txt() 403 return 404 ix = None 405 if not (target_ix is None): 406 ix = target_ix 407 elif not (target_id is None): 408 for i, t in enumerate(self.targets): 409 if t.target_id == target_id: 410 ix = i 411 break 412 if ix is None: 413 ix = self.__target_ix 414 415 try: 416 if auto_id is None: 417 targeted = not ((target_ix is None) and (target_id is None)) 418 if isinstance(item, NbText): 419 qubx.pyenv.env.OnScriptable('Notebook.send(qubx.notebook.NbText("""%s"""%s))' % 420 (item.to_txt(), 421 targeted and (", target_id='%s'" % self.targets[ix].target_id) or "")) 422 elif item.global_name: 423 qubx.pyenv.env.scriptable_if_matching("Notebook.send(%s%s)" % 424 (item.global_name, 425 targeted and (", target_id='%s'" % self.targets[ix].target_id) or ""), 426 [(item.global_name, item)]) 427 428 self.targets[ix].target.nb_send(item) 429 except: 430 traceback.print_exc()
431
432 -class NbTarget_Stdout(object):
433 """Basic type of notebook output which prints everything to the stdout console."""
434 - def __init__(self):
435 self.supports = [NbItem] # [NbText, NbDynText, NbTable, NbChart, NbTrace]
436 - def nb_init(self):
437 pass
438 - def nb_send(self, item):
439 print item.to_txt()
440 441
442 -def Init():
443 """Sets up qubx.notebook.Notebook as the global L{NbController} object.""" 444 global Notebook 445 Notebook = NbController()
446
447 -def RegisterTargets():
448 """Registers the target types defined in this module.""" 449 Notebook.register_target(NbTarget_Stdout(), 'stdout', 'Console', 'Write to console:', None) 450 Notebook.register_target(NbTarget(), 'none', 'Discard', '', None)
451