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

Source Code for Module qubx.dataGTK

   1  """Specialized scope for qub data. 
   2   
   3  Copyright 2008-2014 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 __future__ import with_statement 
  23   
  24  import cProfile 
  25  import cStringIO 
  26  import cairo 
  27  import datetime 
  28  import gc 
  29  import gtk 
  30  import gobject 
  31  import numpy 
  32  import shutil 
  33  import traceback 
  34  import qubx.chop 
  35  import qubx.date 
  36  import qubx.settings 
  37  import qubx.GTK 
  38  import qubx.scope 
  39  import qubx.toolspace 
  40  import qubx.data_types 
  41  import qubx.fit_space 
  42  import qubx.fast.clickmap 
  43  import qubx.fast.data 
  44  import qubopt.amp 
  45  from gtk import gdk 
  46  from gtk import keysyms 
  47  from qubx.accept import * 
  48  from qubx.data_types import * 
  49  from qubx.GTK import pack_item, pack_space, pack_hsep, pack_vsep, pack_label, pack_button, pack_check, pack_radio, pack_scrolled, build_menuitem 
  50  from qubx.pyenv import bind_scriptable 
  51  from qubx.settings import Property, Propertied 
  52  from qubx.util_types import * 
  53  from qubx.table_tree import table_to_tree, tree_to_table 
  54  from qubx.toolspace import ColorInfo 
  55  from itertools import izip, count, chain 
  56  from numpy import * 
  57   
  58  if qubx.toolspace.HAVE_OPENGL: 
  59      try: 
  60          import gtk.gtkgl 
  61          import gtk.gdkgl 
  62          import OpenGL.GL 
  63          import OpenGL.GLU 
  64      except: 
  65          pass 
  66   
  67  TILE_THRESH = 4.0 
  68   
  69  QUBDATA_SEL_THRESH_MS = 300 
  70  UNSET_IDL = 1543209.75 
  71  SIGNAL_CENTER_HIDDEN = -1e9 
  72   
  73  LORES_PIX_PER_DIV = 25.0 
  74   
  75  MAX_DEFAULT_DISPLAY_POINTS = 2000000 
  76   
  77  BW_HINT_EXPIRE_SEC = 60 
  78   
  79  RULER_BASELINE_CONST, RULER_BASELINE_IN_SEG, RULER_BASELINE_IN_SEL = (0, 1, 2) 
  80  COLOR_RULER_BASELINE = ('dataGTK.ruler.baseline', (.85, .15, .6, .3)) 
  81  ColorInfo[COLOR_RULER_BASELINE[0]].label = 'Data measurement baseline label' 
  82   
  83  COLOR_DATA_LAYER_FG = ('dataGTK.layer.fg', (.1, 1, .1, .9)) 
  84  ColorInfo[COLOR_DATA_LAYER_FG[0]].label = 'Data layer foreground' 
  85  COLOR_DATA_LAYER_HOVER = ('dataGTK.layer.hover', (.1, 1, .1, .9)) 
  86  ColorInfo[COLOR_DATA_LAYER_HOVER[0]].label = 'Data layer foreground mouseover' 
  87  COLOR_CLEAR = qubx.toolspace.COLOR_CLEAR 
  88  COLOR_TIMECTL = ('dataGTK.timectl', (1, 1, 1, .8)) 
  89  ColorInfo[COLOR_TIMECTL[0]].label = 'Data segment +/- buttons' 
  90  COLOR_TIMECTL_HOVER = ('dataGTK.timectl.hover', (1, .5, .5, 1)) 
  91  ColorInfo[COLOR_TIMECTL_HOVER[0]].label = 'Data segment +/- buttons mouseover' 
  92  COLOR_SEL = ('dataGTK.sel', (.3, .3, 1, .3)) 
  93  ColorInfo[COLOR_SEL[0]].label = 'Data background selected' 
  94  COLOR_FITS = ('dataGTK.fit', (.8, 0, .2, .8)) 
  95  ColorInfo[COLOR_FITS[0]].label = 'Data fit curve' 
  96  GAUSS_ALPHA_MAX = 0.8 
  97  COLOR_LIST_ALPHA = 0.22 
  98  COLOR_LIST = ('dataGTK.list', (.7,0,.7,COLOR_LIST_ALPHA)) 
  99  ColorInfo[COLOR_LIST[0]].label = 'Data list background' 
 100  COLOR_LIST_SEL = ('dataGTK.list.sel', (1, 1, 1, .8)) 
 101  ColorInfo[COLOR_LIST_SEL[0]].label = 'Data list background of selected item' 
 102  COLOR_LIST_CHECKED = ('dataGTK.list.checked', (1, .7, 0, .8)) 
 103  ColorInfo[COLOR_LIST_CHECKED[0]].label = 'Data list background of checked item' 
 104  COLOR_LIST_LBL_BG = ('dataGTK.list.lbl.bg', (0,0,0,1)) 
 105  ColorInfo[COLOR_LIST_LBL_BG[0]].label = 'Data list labels background' 
 106  COLOR_LIST_LBL_FG = ('dataGTK.list.lbl.fg', (0,1,0,1)) 
 107  ColorInfo[COLOR_LIST_LBL_FG[0]].label = 'Data list labels foreground' 
 108  COLOR_LIST_NAME = ('dataGTK.list.name', (.7,0,.7,.7)) 
 109  ColorInfo[COLOR_LIST_NAME[0]].label = 'Data list name' 
 110  COLOR_LIST_BEAD = ('dataGTK.list.bead', (.8,.4,.7,.9)) 
 111  ColorInfo[COLOR_LIST_BEAD[0]].label = 'Data list menu bead' 
 112  COLOR_LIST_BEAD_LABEL = ('dataGTK.list.bead.label', (1,1,1,1)) 
 113  ColorInfo[COLOR_LIST_BEAD_LABEL[0]].label = 'Data list menu bead number' 
 114  COLOR_BASELINE_NODES = ('dataGTK.baseline.nodes', (.5, .5, 1, .9)) 
 115  ColorInfo[COLOR_BASELINE_NODES[0]].label = 'Data baseline nodes' 
 116  COLOR_TOOLS_LABEL = ('dataGTK.tools.label', (.4, .8, 1, 1)) 
 117  ColorInfo[COLOR_TOOLS_LABEL[0]].label = 'Data toolbar label' 
 118  COLOR_ZOOM_BG = ('dataGTK.zoom.bg', (1, 1, 1, .84)) 
 119  ColorInfo[COLOR_ZOOM_BG[0]].label = 'Data zoom button background' 
 120  COLOR_CLONE_SOURCE = ('dataGTK.clone.source', (0,1,0,COLOR_LIST_ALPHA)) 
 121  ColorInfo[COLOR_CLONE_SOURCE[0]].label = 'Data clone tool source background' 
 122  COLOR_CLONE_DEST = ('dataGTK.clone.dest', (1,0,0,COLOR_LIST_ALPHA)) 
 123  ColorInfo[COLOR_CLONE_DEST[0]].label = 'Data clone tool destination background' 
 124  COLOR_CLONE_DEST_OTHER = ('dataGTK.clone.dest.other', (.8,.5,.5,COLOR_LIST_ALPHA)) 
 125  ColorInfo[COLOR_CLONE_DEST_OTHER[0]].label = 'Data clone tool destination bg (from other source)' 
 126   
 127  DOT_DIVISIONS = 64 
 128   
 129  Tools = ToolRegistry() 
130 131 132 -class TimeControlLayer(qubx.toolspace.Layer):
133 """Floating controls for which/how many segments to show, and what time range. 134 135 @ivar signals: dataview.signals (lo-res), a L{qubx.Table} 136 @ivar timeRange: L{qubx.toolspace.SubLayer_Range} 137 @ivar OnChangeSegments: L{WeakEVent}C{(Layer)} when Iseg/Greal changes 138 @ivar OnChangeShowing: L{WeakEvent}C{(Layer)} when left/right changes 139 @ivar OnChangeSel: L{WeakEvent}(Layer, left, right) when sel_left and/or sel_right change 140 @ivar OnChangeHilite: L{WeakEvent}(Layer, left, right) when hilite changes 141 """
142 - def __init__(self, signals, x):
143 """ 144 @param signals: dataview.signals, a L{qubx.Table} 145 """ 146 qubx.toolspace.Layer.__init__(self, x, -4, -1, 3.5, w_min=50, cBG=qubx.scope.COLOR_SIGNAL_BG) 147 self.__ref = Reffer() 148 self.defer_scriptable_scroll = qubx.pyenv.DeferredScriptableScroll() 149 self.signals = signals 150 self.signals.OnSet += self.__ref(self.__onSetSignal) 151 self.subSegCount = self.add_sublayer(qubx.toolspace.SubLayer_Label('0', 1, 0, x=1, y=0, w=5, h=1.75, 152 color=COLOR_DATA_LAYER_FG, hover_color=COLOR_DATA_LAYER_HOVER)) 153 self.add_sublayer(qubx.toolspace.SubLayer_Label('Segments:', -1, 0, x=7, y=0, w=8, h=1.75, 154 color=COLOR_DATA_LAYER_FG, hover_color=COLOR_DATA_LAYER_HOVER)) 155 self.add_sublayer(qubx.toolspace.SubLayer_Label('<', 0, 0, x=15, y=0, w=2, h=1.75, 156 color=COLOR_TIMECTL, hover_color=COLOR_TIMECTL_HOVER, 157 action=self.__ref(self.seg_back_group))) 158 self.add_sublayer(qubx.toolspace.SubLayer_Label('-', 0, 0, x=17, y=0, w=1, h=1.75, 159 color=COLOR_TIMECTL, hover_color=COLOR_TIMECTL_HOVER, 160 action=self.__ref(self.seg_back_one))) 161 self.segRange = self.add_sublayer(qubx.toolspace.SubLayer_Label('', 0, 0, x=18, y=0, w=12, h=1.75, 162 action=self.__ref(self.click_segRange), 163 color=COLOR_DATA_LAYER_FG, hover_color=COLOR_DATA_LAYER_HOVER)) 164 self.add_sublayer(qubx.toolspace.SubLayer_Label('+', 0, 0, x=30, y=0, w=1, h=1.75, 165 color=COLOR_TIMECTL, hover_color=COLOR_TIMECTL_HOVER, 166 action=self.__ref(self.seg_fwd_one))) 167 self.add_sublayer(qubx.toolspace.SubLayer_Label('>', 0, 0, x=31, y=0, w=2, h=1.75, 168 color=COLOR_TIMECTL, hover_color=COLOR_TIMECTL_HOVER, 169 action=self.__ref(self.seg_fwd_group))) 170 self.add_sublayer(qubx.toolspace.SubLayer_Label('at a time:', 0, 0, x=35, y=0, w=8, h=1.75, 171 color=COLOR_DATA_LAYER_FG, hover_color=COLOR_DATA_LAYER_HOVER)) 172 self.subSegsTogether = self.add_sublayer(qubx.toolspace.SubLayer_Label('1', 0, 0, x=43, y=0, w=5, h=1.75, 173 action=self.__ref(self.click_together), scroll=self.__ref(self.scroll_together), 174 color=COLOR_DATA_LAYER_FG, hover_color=COLOR_DATA_LAYER_HOVER)) 175 self.add_sublayer(qubx.toolspace.SubLayer_UpDown(up=self.__ref(self.up_together), down=self.__ref(self.down_together), 176 x=48, y=0, w=1, h=1.75, color=COLOR_DATA_LAYER_FG)) 177 self.timeRange = self.add_sublayer(qubx.toolspace.SubLayer_Range(0, 1000, 1, x=1, y=1.75, w=-1, h=1.75)) 178 self.timeRange.OnMoving += self.__ref(self.__onMovingTimeRange) 179 self.timeRange.OnSet += self.__ref(self.__onSetTimeRange) 180 181 self.OnChangeSegments = WeakEvent() # (Layer) 182 self.OnChangeShowing = WeakEvent() # (Layer) 183 self.OnChangeSel = WeakEvent() # (Layer, left, right) 184 self.OnChangeHilite = WeakEvent() # (Layer, left, right) 185 self.__Nseg = 0 186 self.__Iseg = 0 187 self.__Gseg = 1 188 self.__Greal = 0 189 self.__Center = 0.0 190 self.__Perdiv = 1.0 191 self.__Ndiv = 2 192 self.__dur = 1.0 193 self.__wid = 1.0 194 self.__left = 0.0 195 self.__right = 1.0 196 self.__sel_left = 0.0 197 self.__sel_right = 0.0 198 self.__hilite = (None, None) 199 self.__file = None 200 self.__durs = [] 201 self.__tMax = 0.0 202 self.Iseg_last = 0 203 self.__lastSampling = 0.0
204 - def set_Nseg(self, x):
205 if self.__Nseg == x: return 206 self.__Nseg = x 207 if x: 208 self.setup_dur() 209 self.subSegCount.label = str(x)
210 - def set_Iseg(self, x):
211 if self.__Iseg == x: return 212 self.setup_dur(x) 213 self.OnChangeSegments(self) 214 self.OnChangeSel(self, self.sel_left, self.sel_right)
215 - def set_Gseg(self, x):
216 if self.__Gseg == x: return 217 self.__Gseg = x 218 self.setup_dur() 219 self.subSegsTogether.label = str(self.__Gseg)
220 - def setup_dur(self, iseg=None):
221 """Adjusts the overall duration when the index or number of segments changes.""" 222 if iseg is None: 223 iseg = self.__Iseg 224 nseg, gseg, actual_g = self.__Nseg, self.__Gseg, self.__Greal 225 if nseg <= 0: 226 nseg = iseg = 0 227 else: 228 gseg = max(1, gseg) 229 iseg = max(0, min(iseg, nseg-gseg)) 230 actual_g = max(1, min(gseg, nseg-iseg)) 231 change = (iseg != self.__Iseg) or (actual_g != self.__Greal) 232 self.__Nseg, self.__Iseg, self.__Gseg, self.__Greal = nseg, iseg, gseg, actual_g 233 self.Iseg_last = iseg + actual_g - 1 234 self.segRange.label = nseg and ('%i to %i' % (iseg+1, iseg+actual_g)) or '' 235 if change: 236 self.set_dur( max(self.__durs[iseg:iseg+actual_g]) ) 237 self.OnChangeSegments(self) 238 self.OnChangeShowing(self) 239 self.OnChangeSel(self, self.sel_left, self.sel_right)
240 - def set_dur(self, dur):
241 self.__dur = dur 242 self.re_range()
243 - def pop_range(self, full=False):
244 self.__wid = min(self.__wid, self.__dur) 245 self.re_range() 246 if full: 247 self.timeRange.set_range(0.0, min(self.timeRange.bounds[1], MAX_DEFAULT_DISPLAY_POINTS*self.file.sampling))
248 - def re_range(self, event_if_moved=True):
249 self.timeRange.set_bounds( (0.0, max(self.__dur, self.__wid)), event_if_moved ) 250 if self.__sel_left >= self.__dur: 251 self.set_sel(0, 0) 252 elif self.__sel_right > self.__dur: 253 self.set_sel(self.__sel_left, self.__dur)
254 - def set_left(self, x):
255 self.timeRange.set_range(x, self.__right)
256 - def set_right(self, x):
257 self.timeRange.set_range(self.__left, x)
258 - def set_sel_left(self, x):
259 self.set_sel(x, max(x, self.__sel_right))
260 - def set_sel_right(self, x):
261 self.set_sel(min(x, self.__sel_left), x)
262 - def set_sel(self, left, right):
263 self.__sel_left = max(0, min(self.timeRange.bounds[1], left)) 264 self.__sel_right = max(0, min(self.timeRange.bounds[1], right)) 265 if self.__sel_left > self.timeRange.right: 266 self.right = right 267 elif self.__sel_right < self.timeRange.left: 268 self.left = left 269 self.OnChangeSel(self, left, right) 270 # adjust hilite 271 hl, hr = self.__hilite 272 if (hl > right) or (hr < left): 273 self.set_hilite(None, None) 274 elif (hl < left) or (hr > right): 275 hl = max(hl, left) 276 hr = min(hr, right) 277 self.set_hilite(hl, hr)
278 - def set_hilite(self, left, right):
279 self.__hilite = (left, right) 280 self.OnChangeHilite(self, left, right)
281 - def set_ndiv(self, x):
282 if x == self.__Ndiv: return 283 self.__Ndiv = x 284 self.__wid = self.__Perdiv * x 285 #print ('re-range from (%f, %f, %f)' % (self.__Center, self.__Perdiv, self.__wid)), 286 self.re_range(event_if_moved=False) 287 #print 'to (%f, %f, %f)' % (self.__Center, self.__Perdiv, self.__wid) 288 halfwid = self.__wid / 2.0 289 self.timeRange.set_range(self.__Center - halfwid, self.__Center + halfwid)
290 Nseg = property(lambda self: self.__Nseg, lambda self,x: self.set_Nseg(x), doc="total number of data segments") 291 Iseg = property(lambda self: self.__Iseg, lambda self,x: self.set_Iseg(x), doc="index of first onscreen segment") 292 Gseg = property(lambda self: self.__Gseg, lambda self,x: self.set_Gseg(x), doc="number of onscreen segments ('at a time')") 293 Greal = property(lambda self: self.__Greal, doc="actual number of onscreen segments e.g. if Nseg < Gseg") 294 left = property(lambda self: self.__left, lambda self,x: self.set_left(x), doc="lower bound of the lo-res onscreen area, in seconds from segment start") 295 right = property(lambda self: self.__right, lambda self,x: self.set_right(x), doc="upper bound of the lo-res onscreen area, in seconds from segment start") 296 sel = property(lambda self: (self.__sel_left, self.__sel_right), lambda self, x: self.set_sel(*x), doc="shorthand for (sel_left, sel_right)") 297 sel_left = property(lambda self: self.__sel_left, lambda self,x: self.set_sel_left(x), doc="lower bound of the lo-res selection, in seconds from segment start") 298 sel_right = property(lambda self: self.__sel_right, lambda self,x: self.set_sel_right(x), doc="upper bound of the hi-res selection, in seconds from segment start") 299 hilite = property(lambda self: self.__hilite, lambda self, x: self.set_hilite(*x), doc="(left, right) of the high-res selection, in seconds from segment start") 300 Ndiv = property(lambda self: self.__Ndiv, lambda self, x: self.set_ndiv(x), doc="number of horizontal divisions (grid lines) because zoom is controlled by 'per div'") 301 file = property(lambda self: self.__file, lambda self,x: self.set_file(x), doc="L{qubx.data_types.QubData} instance which is being viewed; assign before using")
302 - def set_file(self, x):
303 if self.__file: 304 self.__file.segmentation.OnAdd -= self.__ref(self.__onAddSeg) 305 self.__file.segmentation.OnClear -= self.__ref(self.__onClearSegs) 306 self.__file.OnChangeSampling -= self.__ref(self.__onChangeSampling) 307 self.__onClearSegs(None) 308 self.__file = x 309 if self.__file: 310 self.timeRange.quantum = self.__file.sampling 311 self.__file.segmentation.OnAdd += self.__ref(self.__onAddSeg) 312 self.__file.segmentation.OnClear += self.__ref(self.__onClearSegs) 313 self.__file.OnChangeSampling += self.__ref(self.__onChangeSampling) 314 dur = 0 315 for i, seg in enumerate(self.__file.segmentation.segments): 316 self.__onAddSeg(self.__file.segmentation, seg[0], seg[1], self.__file.segmentation.starts[i]) 317 dur = max(dur, seg[1] - seg[0] + 1) 318 dur = dur and (self.__file.sampling * dur) or 1.0 319 gobject.idle_add(self.timeRange.set_bounds, (0.0, dur))
320 - def __onAddSeg(self, segm, f, l, start):
321 dur = self.file.sampling * (l - f + 1) 322 first = not self.__durs 323 self.__durs.append(dur) 324 self.Nseg = len(segm.segments) 325 if first: 326 self.set_dur(dur) 327 self.pop_range(True)
328 - def __onClearSegs(self, segm):
329 self.__durs[:] = [] 330 self.Nseg = 0
331 - def __onChangeSampling(self, file, sampling):
332 if self.__lastSampling: 333 self.set_dur(self.__dur * sampling / self.__lastSampling) 334 self.__lastSampling = sampling
335 - def __onSetTimeRange(self, timeRange, left, right, by_mouse):
336 if by_mouse: 337 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.timeRange.set_range(%s, %s)' % (repr(left), repr(right))) 338 self.__onMovingTimeRange(timeRange, left, right)
339 - def __onMovingTimeRange(self, timeRange, left, right):
340 self.__left = left 341 self.__right = right 342 self.__wid = right - left 343 self.__Perdiv = self.__wid / self.__Ndiv 344 #traceback.print_stack() 345 #print 'onMovingTimeRange: Center %f -> %f (%f, %f)' % (self.__Center, (right+left)/2.0, left, right) 346 self.__Center = (right + left) / 2.0 347 self.signals.set(0, 'Center', self.__Center) 348 self.signals.set(0, 'per div', self.__Perdiv) 349 gobject.idle_add( self.pop_range ) 350 self.OnChangeShowing(self)
351 - def __onSetSignal(self, i, field_name, val, prev, undoing=False):
352 if i == 0: 353 if field_name == 'Center': 354 val = max(self.timeRange.quantum/2.0, min(val, self.timeRange.bounds[1])) 355 if val != self.__Center: 356 halfwid = self.__wid / 2.0 357 self.__Center = val 358 self.timeRange.set_range(val - halfwid, val + halfwid) 359 elif field_name == 'per div': 360 val = max(self.timeRange.quantum/self.__Ndiv, val) 361 if val != self.__Perdiv: 362 self.__Perdiv = val 363 self.__wid = val * self.__Ndiv 364 self.re_range(event_if_moved=False) 365 halfwid = self.__wid / 2.0 366 self.timeRange.set_range(self.__Center-halfwid, self.__Center+halfwid)
367 - def seg_back_group(self, x, y, e):
368 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Iseg -= QubX.Data.view.time.Gseg') 369 self.Iseg = self.Iseg - self.Gseg
370 - def seg_back_one(self, x, y, e):
371 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Iseg -= 1') 372 self.Iseg = self.Iseg - 1
373 - def seg_fwd_group(self, x, y, e):
374 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Iseg += QubX.Data.view.time.Gseg') 375 self.Iseg = self.Iseg + self.Gseg
376 - def seg_fwd_one(self, x, y, e):
377 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Iseg += 1') 378 self.Iseg = self.Iseg + 1
379 - def click_segRange(self, x, y, e):
380 dlg = qubx.GTK.NumEntryDialog('%s - Enter a number'%qubx.pyenv.env.globals['QubX'].appname, 381 None, '[first] segment to show:', 382 self.__Iseg+1, acceptIntBetween(1, max(1, self.Nseg-self.Gseg+1))) 383 if gtk.RESPONSE_ACCEPT == dlg.run(): 384 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Iseg = %i' % (dlg.value - 1)) 385 self.Iseg = dlg.value-1 386 dlg.destroy()
387 - def click_together(self, x, y, e):
388 dlg = qubx.GTK.NumEntryDialog('%s - Enter a number'%qubx.pyenv.env.globals['QubX'].appname, 389 None, 'Segments to show at a time:', 390 self.__Gseg, acceptIntGreaterThan(0)) 391 if gtk.RESPONSE_ACCEPT == dlg.run(): 392 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Gseg = %i' % dlg.value) 393 self.Gseg = dlg.value 394 dlg.destroy()
395 - def scroll_together(self, x, y, e, off):
396 self.defer_scriptable_scroll('Gseg', 397 'QubX.Data.view.time.Gseg = scrolled_int(QubX.Data.view.time.Gseg, %s, lo=1)', 398 off, e.state & gdk.CONTROL_MASK, e.state & gdk.SHIFT_MASK) 399 self.Gseg = scrolled_int(self.Gseg, off, e.state & gdk.CONTROL_MASK, e.state & gdk.SHIFT_MASK, lo=1)
400 - def up_together(self):
401 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Gseg += 1') 402 self.Gseg = self.Gseg + 1
403 - def down_together(self):
404 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Gseg -= 1') 405 self.Gseg = self.Gseg - 1
406 - def zoom_in(self, factor=2.0, left=UNSET_VALUE, right=UNSET_VALUE):
407 if left != UNSET_VALUE: 408 self.set_sel(left, right) 409 else: 410 c = (self.sel_left + self.sel_right) / 2.0 411 hw = ((self.sel_right - self.sel_left) / 2.0) / factor 412 self.set_sel(c-hw, c+hw)
413 - def zoom_out(self, factor=2.0, prefer_undo=True):
414 c = (self.sel_left + self.sel_right) / 2.0 415 hw = factor * (self.sel_right - self.sel_left) / 2.0 416 self.set_sel(max(0, c-hw), min(self.timeRange.bounds[1], c+hw))
417 418 419 #COLOR_POPUP_EDIT = ('dataGTK.popup.edit', (1, 1, .1, 1)) 420 COLOR_MAG = ('dataGTK.popup.mag', (0,1,0,1)) 421 ColorInfo[COLOR_MAG[0]].label = 'Data zoom icon text' 422 COLOR_MAG_HOVER = ('dataGTK.popup.mag.hover', (1,1,1,1)) 423 ColorInfo[COLOR_MAG_HOVER[0]].label = 'Data zoom icon text mouseover'
424 425 426 -class QubDataView_Base(qubx.scope.Scope):
427 """Specialized Scope for qub data files. Originally there was only one view, which later split 428 into lo- and hi-res panes. Accordingly, most of the program recognizes just one QubX.Data.view, which 429 is the lo-res (upper) pane. The hi-res pane is QubX.Data.view.hires. 430 431 @ivar file: L{qubx.data_types.QubData} 432 @ivar time: L{TimeControlLayer} must be provided by subclass (owned by lo-res, shared by both) 433 @ivar OnHint: L{WeakEvent}C{(QubDataView, str)} called with coordinates when the mouse moves 434 @ivar OnListSorted: L{WeakEvent}(TableView) 435 """ 436 437 __explore_featured = ['file', 'time', 'OnHint', 'OnListSorted', 'samp_per_pix', 'draw_bounds', 'tile_cache', 'list_sel', 438 'drawing_dur', 'drawing_spp', 'signals', 'data_overlay', 'file_info', 'label', 'notebook', 439 'nbPicture', 'nbPictureNO', 'nbChart', 'dispose', 'multi_line', 'auto_scale', 'color_idealized', 'gauss_intensity', 440 'raw_bounds', 'draw_dim', 'display_limits', 'mark_overlays', 441 'subtract_baseline_nodes', 'hide_signal', 'center_signal', 'new_list', 'add_screen_to_list', 442 'toggle_grid', 'set_show_grid', 'update_display_prefs', 'update_hint', 'draw_custom_overlay', 443 'get_sampled_events', 'get_sampled_fits', 'get_center_sample', 'get_segmentation_file', 444 'get_segmentation_screen', 'get_segmentation', 'get_segmentation_indexed', 'get_sels_screen', 445 'add_notebook_item', 'nb_get_shape', 'nb_draw', 'nb_draw_no_overlays', 'nb_get_caption', 'nb_iter_table_cols', 446 'nb_get_table_shape', 'nb_get_headers', 'nb_get_col', 'nb_get_col_format', 'nb_get_trace_xlabel', 447 'nb_get_trace_ylabel', 'nb_get_trace_series'] 448
449 - def __init__(self, global_name, signal_controls):
450 qubx.scope.Scope.__init__(self, global_name, signal_controls) 451 self.__ref = Reffer() 452 self.set_size_request(200, 100) 453 self.OnHint = WeakEvent() # (DataView, str) 454 self.OnListSorted = WeakEvent() 455 self._file = None 456 self.OnDraw += self.__ref(self.__onDraw) 457 self.OnDrawGL += self.__ref(self.__onDrawGL) 458 self.OnOverlay += self.__ref(self.__onOverlay) 459 self.enable_grid() 460 self._drawing = False 461 self.samp_per_pix = Product(self.__get_samp_per_pix) 462 self.draw_bounds = Product(self.__get_draw_bounds) 463 self.samp_per_pix.OnInvalidate += self.__ref(bind(self.draw_bounds.invalidate)) 464 self.__raw_bounds = (0.0, 0.0) 465 self.tile_cache = [] 466 self.list_sel = None 467 self.drawing_dur = self.drawing_spp = 0 468 self.signals.global_name = '%s.signals' % global_name 469 self.signals.add_field('Idl above', False, acceptBool, str, '') 470 self.signals.OnSet += self.__ref(self.__onSetSignal) 471 self.signals.OnInsert += self.__ref(self.__onInsertSignal) 472 self.signals.OnRemoving += self.__ref(self.__onRemovingSignal) 473 self.__overlay_surface = self.__gauss_surface = self.__gauss_color = None 474 self.data_overlay = Product(self.__get_data_overlay, 0, 0) 475 self.file_info = Product(self.__get_file_info) 476 self.__mark_overlays = False 477 478 self.label = 'Trace' # for Notebook_Table_Extensions 479 self.notebook = {} 480 self.layNotebook = qubx.toolspace.Layer(1, -4, 2, 3, qubx.toolspace.COLOR_CLEAR) 481 self.controls.add_layer(self.layNotebook) 482 self.subNotebook = qubx.notebookGTK.SubLayer_Notebook(x=0, y=.5, w=2, h=2) 483 self.layNotebook.add_sublayer(self.subNotebook) 484 self.nbPicture = qubx.notebookGTK.NbPicture(mnu_caption='Data image', global_name='%s.nbPicture'%self.global_name, get_shape=self.nb_get_shape, draw=self.nb_draw) 485 self.subNotebook.items.append(self.nbPicture) 486 self.nbPictureNO = qubx.notebookGTK.NbPicture(mnu_caption='Data image, no overlays', global_name='%s.nbPictureNO'%self.global_name, get_shape=self.nb_get_shape, draw=self.nb_draw_no_overlays) 487 self.subNotebook.items.append(self.nbPictureNO) 488 self.nbChart = qubx.notebook.NbTrace(mnu_caption='Data table', global_name='%s.nbChart'%self.global_name, get_caption=self.nb_get_caption, get_shape=self.nb_get_table_shape, get_headers=self.nb_get_headers, get_col=self.nb_get_col, get_col_format=self.nb_get_col_format, get_type=lambda: float, 489 get_xlabel=self.nb_get_trace_xlabel, 490 get_ylabel=self.nb_get_trace_ylabel, 491 get_trace_series=self.nb_get_trace_series) 492 self.subNotebook.items.append(self.nbChart) 493 self.nbChartAll = qubx.notebook.NbTrace(mnu_caption='Data table (all signals)', global_name='%s.nbChartAll'%self.global_name, get_caption=self.nb_get_caption, get_shape=self.nb_get_table_shape, get_headers=self.nb_get_headers, get_col=self.nb_get_col, get_col_format=self.nb_get_col_format, get_type=lambda: float, 494 get_xlabel=self.nb_get_trace_xlabel, 495 get_ylabel=self.nb_get_trace_ylabel, 496 get_trace_series=self.nb_get_trace_series_all) 497 self.subNotebook.items.append(self.nbChartAll) 498 self.nbChartList = qubx.notebook.NbTrace(mnu_caption='Data table (all items in List)', global_name='%s.nbChartList'%self.global_name, get_caption=self.nb_get_caption_list, get_shape=self.nb_get_table_shape_list, get_headers=self.nb_get_headers_list, get_col=self.nb_get_col_list, get_col_format=self.nb_get_col_format, get_type=lambda: float, 499 get_xlabel=self.nb_get_trace_xlabel, 500 get_ylabel=self.nb_get_trace_ylabel, 501 get_trace_series=self.nb_get_trace_series_list) 502 self.subNotebook.items.append(self.nbChartList) 503 504 qubx.notebook.Notebook.register_auto('Measure.Segments', 'Segments table, on measure', True) 505 506 self.layLists = Layer_Lists(w=-1, h=1.5) 507 self.controls.add_layer(self.layLists) 508 self.QubX = qubx.pyenv.env.globals['QubX'] 509 self.__show_grid = True 510 gobject.idle_add(self.__init_show_grid) 511 self.hide_hidden = self.appearance.hide_hidden_signals 512 self.appearance.OnSetHideHiddenSignals += self.__ref(self.__onSetHideHidden) 513 self.__multi_line = self.appearance.multi_line_data 514 self.appearance.OnSetMultiLineData += self.__ref(self.__onSetMultiLine) 515 self.__auto_scale = self.appearance.auto_scale_data 516 self.appearance.OnSetAutoScaleData += self.__ref(self.__onSetAutoScale) 517 self.__color_idealized = self.appearance.color_idealized 518 self.appearance.OnSetColorIdealized += self.__ref(self.__onSetColorIdealized) 519 self.__gauss_intensity = self.appearance.gauss_intensity 520 self.appearance.OnSetGaussIntensity += self.__ref(self.__onSetGaussIntensity) 521 self.__subtract_baseline_nodes = True 522 self.__hint_serial = 0
523 - def dispose(self):
524 """Disconnects events and such when this object is no longer needed, making garbage collection more likely.""" 525 self.layLists.disconnect_space(self) 526 self.QubX = None 527 self.file = None 528 self.signals.dispose() 529 self.__ref.clear() 530 del self.tile_cache[:]
531 - def set_multi_line(self, x):
532 self.__multi_line = x
533 multi_line = property(lambda self: self.__multi_line, lambda self, x: self.set_multi_line(x), "True to split data across multiple traces, as space allows (this object subscribes to qubx.toolspace.Appearance's global setting)")
534 - def set_auto_scale(self, x):
535 self.__auto_scale = x
536 auto_scale = property(lambda self: self.__auto_scale, lambda self, x: self.set_auto_scale(x), "True to re-center visible signals whenever the lo-res onscreen area (time.left, time.right) changes.")
537 - def set_color_idealized(self, x):
538 self.__color_idealized = x
539 color_idealized = property(lambda self: self.__color_idealized, lambda self, x: self.set_color_idealized(x), "True to draw idealized data with class colors")
540 - def set_gauss_intensity(self, x):
541 self.__gauss_intensity = x
542 gauss_intensity = property(lambda self: self.__gauss_intensity, lambda self, x: self.set_gauss_intensity(x), "True to draw MacRates fit with intensity gradient")
543 - def set_mark_overlays(self, x):
544 self.__mark_overlays = x 545 self.redraw_canvas(True)
546 mark_overlays = property(lambda self: self.__mark_overlays, lambda self, x: self.set_mark_overlays(x))
547 - def __get_samp_per_pix(self):
548 if not self.signals: 549 return 1.0 550 spp = self.signals[0, 'per div'] / (self.file.sampling * self.pix_per_div[0]) 551 for tc in self.tile_cache: 552 tc.samp_per_pix = spp 553 return spp
554 - def __get_draw_bounds(self):
555 left, right = self.__raw_bounds 556 dt = self.file.sampling 557 if not dt: 558 return (0.0, 0.0) 559 spp = self.samp_per_pix.val 560 i_left, i_right = int(round(left/dt)), int(round(right/dt)) 561 # adjust to multiples of samp_per_pix: 562 i_left = int(round(spp * int(i_left / spp))) 563 i_right = int(round(spp * ceil(i_right / spp))) 564 if not self._drawing: 565 self.redraw_canvas() 566 return (dt * i_left, dt * i_right)
567 - def set_draw_dim(self, x):
568 if self.draw_dim != x: 569 qubx.scope.Scope.set_draw_dim(self, x) 570 self.samp_per_pix.invalidate() 571 for tc in self.tile_cache: 572 tc.tile_width = x[0] 573 tc.clear() # in case only height changed
574 - def set_raw_bounds(self, lr):
575 if lr != self.__raw_bounds: 576 if abs((self.__raw_bounds[1] - self.__raw_bounds[0]) - (lr[1] - lr[0])) > (self.file.sampling / 2.0): 577 self.samp_per_pix.invalidate() 578 else: 579 self.draw_bounds.invalidate() 580 self.__raw_bounds = lr 581 self.redraw_canvas(True)
582 raw_bounds = property(lambda self: self.__raw_bounds, lambda self, x: self.set_raw_bounds(x))
583 - def set_time(self, x):
584 self.__time = x 585 self.layLists.connect_space(self) 586 self.time.OnChangeSegments += self.__ref(self.__onChangeSegments)
587 time = property(lambda self: self.__time, lambda self, x: self.set_time(x))
588 - def display_limits(self, ix):
589 """Returns (lo, hi) onscreen limits of signal with Index==ix in the Scope table.""" 590 c = self.signals[ix, 'Center'] 591 d = self.signals[ix, 'per div'] 592 n = self.divs[1] / 2 593 return (c-d, c+d)
594 - def __onSetHideHidden(self, x):
595 self.hide_hidden = x
596 - def __onSetMultiLine(self, x):
597 self.multi_line = x
598 - def __onSetAutoScale(self, x):
599 self.auto_scale = x
600 - def __onSetColorIdealized(self, x):
601 self.color_idealized = x 602 self.redraw_canvas()
603 - def __onSetGaussIntensity(self, x):
604 self.gauss_intensity = x 605 self.redraw_canvas()
606 - def __init_show_grid(self):
607 self.__show_grid = bool(self.appearance.color(qubx.scope.COLOR_GRID)[3])
608 show_grid = property(lambda self: self.__show_grid, lambda self, x: self.set_show_grid(x), "True to show grid lines; global pref implemented via alpha channel of appearance prefs for qubx.scope.COLOR_GRID")
609 - def set_subtract_baseline_nodes(self, x):
610 self.__subtract_baseline_nodes = x 611 self.redraw_canvas(True)
612 subtract_baseline_nodes = property(lambda self: self.__subtract_baseline_nodes, lambda self, x: self.set_subtract_baseline_nodes(x), "True to subtract linear interpolated baseline from data for display (e.g. on hi-res panel while baseline cursor is selected).")
613 - def __onChangeSegments(self, time):
614 if not self._drawing: 615 self.redraw_canvas() 616 for tc in self.tile_cache: 617 tc.set_segs_onscreen(time.Iseg, time.Greal)
618 - def set_file(self, file):
619 if self._file: 620 if self._file.signals: # not blank 621 self._file.OnChangeSamples -= self.__ref(self.__onChangeSamples) 622 self._file.OnChangeIdealization -= self.__ref(self.__onChangeIdealization) 623 self._file.OnChangeFits -= self.__ref(self.__onChangeFits) 624 self._file.lists.OnSwitchList -= self.__ref(self.__onSwitchList) 625 self._file.lists.OnChangeList -= self.__ref(self.__onChangeList) 626 self._file.OnChangeOverlaySource -= self.__ref(self.__onChangeOverlays) 627 self._file.OnChangeOverlays -= self.__ref(self.__onChangeOverlays) 628 self._file = file 629 self.layLists.file = file 630 if file: 631 if file.signals: # not blank 632 self._file.OnChangeSamples += self.__ref(self.__onChangeSamples) 633 self._file.OnChangeIdealization += self.__ref(self.__onChangeIdealization) 634 self._file.OnChangeFits += self.__ref(self.__onChangeFits) 635 self._file.lists.OnSwitchList += self.__ref(self.__onSwitchList) 636 self._file.lists.OnChangeList += self.__ref(self.__onChangeList) 637 self._file.OnChangeOverlaySource += self.__ref(self.__onChangeOverlays) 638 self._file.OnChangeOverlays += self.__ref(self.__onChangeOverlays) 639 self.file_info.invalidate()
640 file = property(lambda self: self._file, lambda self,x: self.set_file(x))
641 - def __onSetSignal(self, i, field_name, val, prev, undoing=False):
642 self.redraw_canvas() 643 if i > 0: 644 if field_name == 'Center': 645 self.tile_cache[i-1].center = val 646 self.tile_cache[i-1].clear() 647 if val == SIGNAL_CENTER_HIDDEN: 648 self.tile_cache[i-1].tiles_per_seg = 0 649 elif prev == SIGNAL_CENTER_HIDDEN: 650 self.tile_cache[i-1].tiles_per_seg = 3*self.line_count 651 elif field_name == 'per div': 652 self.tile_cache[i-1].per_div = val 653 self.tile_cache[i-1].clear() 654 else: 655 return 656 self.tile_cache[i-1].clear()
657 - def __onInsertSignal(self, i, undoing=False):
658 if i == 0: return 659 tc = TileCache(i-1, tile_width=(self._dim[0] or 1024)) 660 tc.center = self.signals[i, 'Center'] 661 tc.per_div = self.signals[i, 'per div'] 662 self.tile_cache.insert(i-1, tc) 663 for j in xrange(i, len(self.tile_cache)): 664 self.tile_cache[j].i_signal = j 665 self.signals.user_can_remove[i] = False
666 - def __onRemovingSignal(self, i, undoing=False):
667 if i == 0: return 668 del self.tile_cache[i-1] 669 for j in xrange(i-1, len(self.tile_cache)): 670 self.tile_cache[j].i_signal = j
671 - def __onChangeSamples(self, *args):
672 for tc in self.tile_cache: 673 tc.clear() 674 self.redraw_canvas(True) 675 self.file_info.invalidate()
676 - def __onChangeIdealization(self, *args):
677 self.redraw_canvas(True)
678 - def __onChangeFits(self, *args):
679 self.data_overlay.invalidate() 680 self.redraw_canvas(False)
681 - def __onSwitchList(self, lists, index, lst):
682 if lst: 683 qubx.pyenv.env.scriptable_if_matching('QubX.Data.file.lists.show_list(%s)' % repr(lst.list_name), 684 [(self.global_name, self)]) 685 self.list_sel = None 686 self.redraw_canvas() 687 self.layLists.subList.list = lst
688 - def __onChangeList(self, lists, lst):
689 self.redraw_canvas()
690 - def __onChangeOverlays(self, file, i):
691 if i == qubx.global_namespace.QubX.DataSource.signal: 692 self.redraw_canvas(True)
693 - def hide_signal(self, index):
694 """Hides a signal (by its index in Scope table), by setting Scope[index, 'Center'] to a special value.""" 695 self.signals.set(index, 'Center', SIGNAL_CENTER_HIDDEN) 696 self.signals.set(index, 'per div', 1)
697 - def center_signal(self, index):
698 """Adjusts a signal's 'Center' and 'per div' in the Scope table, to bring the lo-res region fully onscreen; 699 for signal 0 (time) this expands lo-res to show the entire segment.""" 700 i = index - 1 701 lo = hi = UNSET_VALUE 702 segs = self.get_segmentation_screen() 703 if self.file.analog[i]: 704 for chunk in generate_chunk_samples(chain(*[seg.chunks for seg in segs]), signal=i): 705 if chunk.included: 706 seglo = numpy.min(chunk.samples) 707 seghi = numpy.max(chunk.samples) 708 if (lo == UNSET_VALUE) or (seglo < lo): 709 lo = seglo 710 if (hi == UNSET_VALUE) or (seghi > hi): 711 hi = seghi 712 else: 713 for seg in segs: 714 amp = self.file.ideal[i].seg[seg.index].amp 715 if amp: 716 for chunk in seg.chunks: 717 if chunk.included: 718 ff, ll, cc = chunk.get_idealization(i, mark_excluded=True) 719 for c in cc: 720 if c >= 0: 721 a = amp[c] 722 if (lo == UNSET_VALUE) or (a < lo): 723 lo = a 724 if (hi == UNSET_VALUE) or (a > hi): 725 hi = a 726 if lo != UNSET_VALUE: 727 flatline = False 728 if hi == lo: 729 hi = lo + 1 730 flatline = True 731 nsig = float(self.file.signals.size) 732 perdiv = (hi - lo) * 1.7 / (self.divs[1] - 1) # * nsig 733 self.signals.set(i+1, 'per div', perdiv) 734 self.signals.set(i+1, 'Center', (lo + hi)/2.0 - (hi - lo)/6.0 + ((not flatline) and i*perdiv*self.divs[1]*.1/nsig or 0.0)) # + ((i+1.0)/(nsig+1.0) - 0.5)*self.divs[1]*perdiv)
735 - def new_list(self):
736 """Prompts the user to enter the name of a new selection list; creates it (if nonexistant); and brings it to front.""" 737 dlg = qubx.GTK.NumEntryDialog('New list...', qubx.pyenv.env.globals['QubX'], 738 'Name of the new list:', 'Untitled') 739 if gtk.RESPONSE_ACCEPT == dlg.run(): 740 qubx.pyenv.env.OnScriptable('QubX.Data.file.lists.show_list(%s)' % repr(dlg.value)) 741 self.file.lists.show_list(dlg.value) 742 qubx.pyenv.env.globals['QubX'].Tables.show_table(self.file.list) 743 dlg.destroy()
744 - def add_screen_to_list(self):
745 """Adds onscreen region to selection list; for hi-res pane this can be time.hilite if it exists.""" 746 if (self.file.list is None) or (not self.file.list.user_can_edit): 747 return 748 qubx.pyenv.env.OnScriptable('for f, l in %s.get_sels_screen(hilite=True): QubX.Data.file.list.insert_selection(f, l)' % self.global_name) 749 for f, l in self.get_sels_screen(hilite=True): 750 self.file.list.insert_selection(f, l)
751 - def toggle_grid(self):
752 """Alternately makes grid lines in/visible.""" 753 self.show_grid = not self.show_grid
754 - def set_show_grid(self, x):
755 self.__show_grid = bool(x) 756 cG = list(self.appearance.color(qubx.scope.COLOR_GRID)) 757 cM = list(self.appearance.color(qubx.scope.COLOR_MEAN_LINE)) 758 for c in cG, cM: 759 c[3] = x and qubx.scope.GRID_ALPHA or 0.0 760 self.appearance.color_preset('scope.grid', tuple(cG)) 761 self.appearance.color_preset('scope.mean.line', tuple(cM))
762 - def update_display_prefs(self, category, props):
763 updates = {} 764 if props.find('Row'): # old tree_to_table style 765 for row in qubx.tree.children(props, 'Row'): 766 try: 767 if row['Center'].data.type == qubx.tree.QTR_TYPE_STRING: # table_to_tree stringifies everything 768 updates[str(row['Name'].data)] = (float(str(row['Center'].data)), float(str(row['per div'].data))) 769 else: # except for some presets created when the signals table had bad format functions: 770 updates[str(row['Name'].data)] = (row['Center'].data[0], row['per div'].data[0]) 771 except: 772 traceback.print_exc() 773 else: 774 tbl = qubx.table.SimpleTable('tmp', auto_add_fields=True) 775 tree_to_table(props, tbl) 776 for row in tbl: 777 updates[row.Name] = (row.Center, row.fields['per div']) 778 for i in xrange(1, self.signals.size): 779 sig_name = self.signals[i, 'Name'] 780 if sig_name in updates: 781 self.signals[i, 'Center'], self.signals[i, 'per div'] = updates[sig_name]
782 - def motion_notify(self, widget, event):
783 qubx.scope.Scope.motion_notify(self, widget, event) 784 if not self.signals: 785 return 786 self.update_hint(event.x, event.y)
787 - def update_hint(self, x, y):
788 self.__hint_serial += 1 789 gobject.idle_add(self.__update_hint, self.__hint_serial, x, y)
790 - def __update_hint(self, serial, x, y):
791 if serial != self.__hint_serial: 792 return 793 sel_info = "Selection: %.3g to %.3g ms" % (1e3*self.time.sel_left, 1e3*self.time.sel_right) 794 signals = [(self.signals.get(0, 'Name'), 795 self.x2u(x, y, self.signals.get(0, 'Center'), self.signals.get(0, 'per div')), 796 self.signals.get(0, 'Units'))] 797 signals += [(self.signals.get(i, 'Name'), 798 self.y2u(y, self.signals.get(i, 'Center'), self.signals.get(i, 'per div')), 799 self.signals.get(i, 'Units')) 800 for i in xrange(1, self.signals.size) 801 if self.signals[i, 'Center'] != SIGNAL_CENTER_HIDDEN] 802 self.OnHint(self, ('%s\n\n%s\n\n' % (self.file_info.val, sel_info)) + '\n'.join(['%s:\t%12.6g %s' % sig for sig in signals]))
803 - def __get_file_info(self):
804 if not self.file.signals: 805 return "" 806 sum_ndata = sum(n for f,l,n in self.file.segmentation.segments) 807 return """Sampling: %.3g kHz (%.3g msec) 808 %i datapoints in %i segments 809 %s total time""" % (1e-3 / self.file.sampling, 1e3 * self.file.sampling, 810 sum_ndata, len(self.file.segments), 811 qubx.date.SecondsToHMS(sum_ndata * self.file.sampling, min_places=2))
812 - def __onDraw(self, context, w, h):
813 if not self.file: return 814 if not self.signals: return 815 draw_bounds = self.draw_bounds.val 816 if draw_bounds[0] == draw_bounds[1]: return 817 t_per_div = self.signals.get(0, 'per div') 818 if not t_per_div: return 819 t_start = datetime.datetime.now() 820 821 self.data_overlay.invalidate() 822 self.__setup_gauss(h) 823 # qubx.pyenv.env.globals['func_to_profile'] = lambda: self.__do_draw(context, w, h) 824 # cProfile.run("func_to_profile()", "/dev/shm/p") 825 #def __do_draw(self, context, w, h): 826 self._drawing = True 827 #dx = self.file.sampling * self.pix_per_div[0] / t_per_div # pixels per sample, with no downsampling 828 #spp = 1.0 / dx 829 spp = self.samp_per_pix.val 830 dx = 1.0 / spp 831 if spp >= 4.0: 832 maxlen = int(round(max(spp, spp * int(round((1<<16)/spp))))) # adjust to even pixel bounds 833 else: 834 maxlen = (1<<16) 835 836 line_height = h / self.line_count 837 for line in xrange(self.line_count): 838 context.save() 839 context.translate(0, line * line_height) 840 context.scale(1.0, 1.0/self.line_count) 841 context.rectangle(0, 0, w, h) 842 context.clip() 843 844 signal_segs = [self.get_segmentation_screen(signal=i, line=line, baseline_nodes=self.subtract_baseline_nodes) for i in xrange(self.file.signals.size)] 845 Nseg = len(signal_segs[0]) 846 Nsigseg = 0 847 signal_cpd = [ [self.signals.get(i+1, x) for x in ('Center', 'per div')] 848 for i in xrange(self.file.signals.size) ] 849 for s in xrange(self.file.signals.size): 850 if self.file.analog[s-1] and (SIGNAL_CENTER_HIDDEN != signal_cpd[s][0]): 851 Nsigseg += Nseg 852 853 for s, signals in enumerate( izip(*signal_segs) ): 854 seg = signals[0] 855 seg_n = seg.l - seg.f + 1 856 seg_dur = seg_n * self.file.sampling 857 seg_w = int(ceil(w * seg_dur / ((draw_bounds[1] - draw_bounds[0])/self.line_count))) 858 self.draw_list(context, w, h, self.file.list, seg, spp) 859 for j, seg in enumerate(signals): 860 if self.file.overlays[qubx.global_namespace.QubX.DataSource.signal]: 861 self.draw_overlays(context, w, h, self.file.overlays[qubx.global_namespace.QubX.DataSource.signal], seg, spp) 862 if spp > TILE_THRESH: 863 tile_lo = numpy.zeros(dtype='int16', shape=(w,)) 864 tile_hi = numpy.zeros(dtype='int16', shape=(w,)) 865 tile_gs_lo = numpy.zeros(dtype='int16', shape=(w,)) 866 tile_gs_hi = numpy.zeros(dtype='int16', shape=(w,)) 867 for i, chunks in enumerate(self.file.analog[j] and iter(seg.chunks) or seg for j, seg in enumerate(signals)): 868 center, per_div = signal_cpd[i] 869 if SIGNAL_CENTER_HIDDEN == center: continue 870 if chunks.next: # has sampled data 871 for chunk in chunks: 872 sig_color = chunk.included and qubx.scope.COLOR_SIGNAL(self.signals.get(i+1, 'Name')) or qubx.scope.COLOR_EXCLUDED 873 chunk_N = self.tile_cache[i].read(self, seg.index, chunk.f-seg.offset, tile_lo, tile_hi, tile_gs_lo, tile_gs_hi, Nsigseg, self.subtract_baseline_nodes) 874 self.draw_samples_raster(context, w, h, sig_color, chunk_N, tile_lo, tile_hi, tile_gs_lo, tile_gs_hi, int(round(dx * (chunk.f - seg.f)))) 875 self.draw_idl(context, w, h, center, per_div, i, chunk, offset=chunk.f-seg.f) 876 else: 877 self.draw_idl(context, w, h, center, per_div, i, chunks) 878 else: 879 for i, chunks in enumerate(self.file.analog[j] and generate_chunk_samples(seg.chunks, signal=j, get_excluded=True, downsample_w=w, downsample_Nsigseg=Nsigseg, maxlen=maxlen) or seg for j, seg in enumerate(signals)): 880 center, per_div = signal_cpd[i] 881 if SIGNAL_CENTER_HIDDEN != center: 882 if chunks.next: # is iter of sampled chunks 883 for chunk in chunks: 884 samples, dt = chunk.samples, chunk.sampling 885 sig_color = chunk.included and qubx.scope.COLOR_SIGNAL(self.signals.get(i+1, 'Name')) or qubx.scope.COLOR_EXCLUDED 886 self.draw_samples(context, w, h, center, per_div, sig_color, samples, dt, dx*(chunk.f-seg.f), seg_n) 887 self.draw_idl(context, w, h, center, per_div, i, chunk, offset=chunk.f-seg.f) 888 else: 889 self.draw_idl(context, w, h, center, per_div, i, chunks) 890 context.restore() 891 self._drawing = False 892 self.drawing_dur = (datetime.datetime.now() - t_start) 893 self.drawing_spp = spp
894 - def __setup_gauss(self, h):
895 # +/- 5 stds 896 cFit = self.appearance.color(COLOR_FITS) 897 hue, sat, val = RGBtoHSV(*cFit[:3]) 898 hue += 0.5 899 if hue > 1: 900 hue -= 1 901 cFitCenter = HSVtoRGB(hue, sat, val) 902 gh = 2**int(ceil(log( (h+1) or (self.__gauss_surface and self.__gauss_surface.get_height()) or 16 )/log(2))) - 1 903 if self.__gauss_surface and (gh == self.__gauss_surface.get_height()) and (cFit == self.__gauss_color): 904 return 905 self.__gauss_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, gh) 906 self.__gauss_color = cFit 907 alpha = numpy.arange(gh, dtype='float32') 908 alpha *= 10.0/gh 909 alpha -= 5.0 910 alpha *= alpha 911 alpha /= -2 912 alpha = numpy.exp(alpha) 913 alpha *= 1.0 / 2*pi 914 alpha *= GAUSS_ALPHA_MAX / numpy.max(alpha) 915 ctx = cairo.Context(self.__gauss_surface) 916 for i, a in enumerate(alpha): 917 ctx.set_source_rgba(* SETALPHA(cFit, a)) 918 ctx.rectangle(0, i, 1, 1) 919 ctx.fill() 920 y = (gh-1)/2 921 dy = max(1, gh/64) 922 ctx.set_source_rgb(*cFitCenter) 923 ctx.rectangle(0, y-dy, 1, dy) 924 ctx.fill()
925 - def __get_data_overlay(self, w, h):
926 if w <= 0: 927 return None 928 t_per_div = self.signals.get(0, 'per div') 929 if not t_per_div: return None 930 if (not self.__overlay_surface) or (w != self.__overlay_surface.get_width()) or (h != self.__overlay_surface.get_height()): 931 self.__overlay_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h) 932 self.__setup_gauss(h) 933 934 context = cairo.Context(self.__overlay_surface) 935 context.set_operator(cairo.OPERATOR_SOURCE) 936 context.set_source_rgba(0,0,0,0) 937 context.paint() 938 context.set_operator(cairo.OPERATOR_OVER) 939 940 line_height = h / self.line_count 941 for line in xrange(self.line_count): 942 context.save() 943 context.scale(1.0, 1.0/self.line_count) 944 context.translate(0, line * line_height) 945 946 segs = self.get_segmentation_screen(line=line) 947 dx = self.file.sampling * self.pix_per_div[0] / t_per_div # pixels per sample, with no downsampling 948 self._drawing = True 949 for sig in xrange(self.file.signals.size): 950 center, per_div = [self.signals.get(sig+1, x) for x in ('Center', 'per div')] 951 if center != SIGNAL_CENTER_HIDDEN: 952 for seg in segs: 953 self.draw_fit(context, w, h, center, per_div, sig, seg) 954 self._drawing = False 955 context.restore() 956 957 self.draw_custom_overlay(context, w, h) 958 959 return self.__overlay_surface
960 - def draw_custom_overlay(self, context, w, h):
961 pass
962 - def __onOverlay(self, context, w, h):
963 if not self.file: return 964 if not self.signals: 965 return 966 surface = self.data_overlay.get_val(w, h) 967 if not surface: return 968 context.set_source_surface(surface) 969 context.rectangle(0, 0, w, h) 970 context.fill()
971 - def draw_list(self, context, w, h, lst, seg, spp):
972 if (not lst) or (lst.list_name == 'Segments'): 973 return 974 # label y0 is .5em below scope controls 975 context.set_line_width(2.0) 976 sels = lst.selections_in(seg.f, seg.l, trimmed=True) 977 for From,To,Label,Index in sels: 978 g = lst[Index,'Group'] 979 px, px2 = [(i-seg.f)/spp for i in (From, To+1)] 980 context.rectangle(px, 1, px2-px, h-2) 981 if Index == self.list_sel: 982 context.set_source_rgba(*self.appearance.color(COLOR_LIST_SEL)) 983 context.stroke_preserve() 984 if g: 985 context.set_source_rgba(* SETALPHA(self.appearance.color(COLOR_CLASS(g)), COLOR_LIST_ALPHA)) 986 else: 987 context.set_source_rgba(*self.appearance.color(COLOR_LIST)) 988 context.fill() 989 y0 = self.appearance.emsize * (1 + qubx.scope.SIGNAL_HEIGHT + .5) 990 xr = -100 991 for From,To,Label,Index in sels: 992 if not Label: continue 993 px = (From-seg.f)/spp 994 if px >= xr: 995 y = y0 996 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents() 997 xbearing, ybearing, wid, hgt, xadvance, yadvance = context.text_extents(Label) 998 vtotal = fascent + fdescent 999 context.rectangle(px, y, wid, vtotal) 1000 context.set_source_rgba(*self.appearance.color(COLOR_LIST_LBL_BG)) 1001 context.fill() 1002 context.set_source_rgba(*self.appearance.color(COLOR_LIST_LBL_FG)) 1003 context.move_to(px-xbearing, y+fascent) 1004 context.show_text(Label) 1005 xr = px+wid 1006 y += vtotal
1007 - def draw_overlays(self, context, w, h, overlays, seg, spp):
1008 if not len(overlays.sels): return 1009 def highlight(From, To): 1010 px, px2 = [(i-seg.f)/spp for i in (From, To+1)] 1011 context.rectangle(px, 1, px2-px, h-2) 1012 context.fill()
1013 f, l = overlays.sels[overlays.cls] 1014 if f < seg.f: 1015 f = seg.f 1016 if l > seg.l: 1017 l = seg.l 1018 if f <= l: 1019 context.set_source_rgba(*self.appearance.color(COLOR_CLONE_SOURCE)) 1020 highlight(f, l) 1021 ff, ll, cc = overlays.idl.get_dwells(seg.f, seg.l, True) 1022 if len(cc): 1023 active = overlays.cls 1024 for f, l, c in izip(ff, ll, cc): 1025 if c == active: 1026 context.set_source_rgba(*self.appearance.color(COLOR_CLONE_DEST)) 1027 else: 1028 context.set_source_rgba(*self.appearance.color(COLOR_CLONE_DEST_OTHER)) 1029 highlight(f, l)
1030 - def draw_fit(self, context, w, h, center, per_div, sig, seg):
1031 t_center, t_per_div = [self.signals.get(0, x) for x in ('Center', 'per div')] 1032 ppd_x, ppd_y = self.pix_per_div 1033 ppsec = ppd_x / t_per_div 1034 seg_n = seg.l - seg.f + 1 1035 seg_dur = seg_n * self.file.sampling 1036 seg_w = int(ceil(seg_dur * ppsec)) 1037 seg_index = self.file.segmentation.index_at(seg.f) 1038 lows, highs = self.get_sampled_fits(sig, seg_index, seg.f, seg.l, seg_w) 1039 self.draw_sampled_idl(context, w, h, center, per_div, seg, lows, highs, color_inc=COLOR_FITS, 1040 gauss_intensity=self.__gauss_intensity and ((not hasattr(qubx.global_namespace.QubX, 'Modeling')) or (not hasattr(qubx.global_namespace.QubX.Modeling, 'MacRates')) or (not qubx.global_namespace.QubX.Modeling.MacRates.optimizing)))
1041 - def draw_samples(self, context, w, h, center, per_div, color, samples, dt, x0, seg_n):
1042 # samples start at draw_bounds[0] 1043 halfwid = w / 2 1044 t_center, t_per_div = [self.signals.get(0, x) for x in ('Center', 'per div')] 1045 ppd_x, ppd_y = self.pix_per_div 1046 ppsec = ppd_x / t_per_div 1047 dx = dt * ppd_x / t_per_div 1048 x = x0 1049 # amp = h/2 + -(ppd_y / per_div) * (amp - center) 1050 samples -= center 1051 samples *= - ppd_y / per_div 1052 samples += h/2 1053 1054 color_tup = self.appearance.color(color) 1055 context.set_source_rgba(* color_tup) 1056 one = self.appearance.line_width * self.appearance.emsize/12.0 1057 1058 if seg_n < (w/4): # draw dots and lines 1059 context.set_line_width(one) 1060 for i, y in enumerate(samples): 1061 if i: 1062 context.line_to(x, y) 1063 context.stroke() 1064 context.arc(x, y, 1.5*one, 0, 2*pi) 1065 context.fill() 1066 context.move_to(x, y) 1067 x += dx 1068 elif seg_n < 4*w: # draw lines 1069 context.set_line_width(one) 1070 for i, y in enumerate(samples): 1071 if i: 1072 context.line_to(x, y) 1073 context.stroke() 1074 context.move_to(x, y) 1075 x += dx
1076 - def draw_samples_raster(self, context, w, h, sig_color, N, lows, highs, gauss_los, gauss_his, pixels_from_start):
1077 color_tup = self.appearance.color(sig_color) 1078 context.set_source_rgba(* color_tup) 1079 cPartial = qubx.toolspace.SETALPHA(color_tup, .5*color_tup[3]) 1080 context.set_line_width(1.0) 1081 context.set_source_rgba(*cPartial) 1082 for i in xrange(N): 1083 context.move_to(pixels_from_start+i, lows[i]) 1084 context.line_to(pixels_from_start+i, highs[i]) 1085 context.stroke() 1086 context.set_source_rgba(*color_tup) 1087 for i in xrange(N): 1088 context.move_to(pixels_from_start+i, gauss_los[i]) 1089 context.line_to(pixels_from_start+i, gauss_his[i]) 1090 context.stroke()
1091 - def draw_idl(self, context, w, h, center, per_div, sig, seg, offset=0):
1092 t_center, t_per_div = [self.signals.get(0, x) for x in ('Center', 'per div')] 1093 t_left = t_center - self.divs[0] * t_per_div / 2 1094 ppd_x, ppd_y = self.pix_per_div 1095 ppsec = ppd_x / t_per_div 1096 seg_n = seg.l - seg.f + 1 1097 seg_dur = seg_n * seg.file.sampling 1098 seg_w = int(ceil(seg_dur * ppsec)) 1099 seg_index = self.file.segmentation.index_at(seg.f) 1100 lows, highs, class_bits = self.get_sampled_events(sig, seg_index, seg.f, seg.l, seg_w) 1101 context.save() 1102 context.translate(int(seg.file.sampling * offset * ppsec), 0) 1103 if self.signals[sig+1, 'Idl above']: 1104 cc = qubx.global_namespace.QubX.Models.file.classes 1105 context.translate(0, -(abs(cc[1,'Amp'] - cc[0,'Amp']) + cc[1,'Std'])/(per_div / ppd_y)) 1106 if self.color_idealized: 1107 self.draw_color_idl(context, seg_w, h, center, per_div, seg, lows, highs, class_bits) 1108 else: 1109 self.draw_sampled_idl(context, seg_w, h, center, per_div, seg, lows, highs, gauss_intensity=False) 1110 context.restore()
1111 - def draw_sampled_idl(self, context, w, h, center, per_div, seg, lows, highs, color_inc=qubx.scope.COLOR_IDEALIZATION, color_exc=qubx.scope.COLOR_EXCLUDED, gauss_intensity=True):
1112 if not numpy.any(lows != UNSET_IDL): 1113 return 1114 dt = self.file.sampling 1115 halfwid = w / 2 1116 t_center, t_per_div = [self.signals.get(0, x) for x in ('Center', 'per div')] 1117 ppd_x, ppd_y = self.pix_per_div 1118 ppsec = ppd_x / t_per_div 1119 1120 context.save() 1121 context.translate(0, h/2) 1122 context.scale(1, -ppd_y / per_div) 1123 context.translate(0, -center) 1124 units_per_pix = per_div / ppd_y 1125 1126 context.set_line_width(self.appearance.line_width*self.appearance.emsize/8.0) 1127 chunks = seg.chunks = [seg] 1128 for chunk in chunks: 1129 if chunk.included: 1130 context.set_source_rgba(*self.appearance.color(color_inc)) 1131 else: 1132 context.set_source_rgba(*self.appearance.color(color_exc)) 1133 npix = len(lows) 1134 i0 = max(0, min(npix-1, int(round(npix*float(chunk.f -seg.f)/seg.n)))) 1135 i1 = max(0, min(npix, int(round(npix*float(chunk.l+1-seg.f)/seg.n)))) 1136 p0 = int(round((chunk.f - seg.f)*dt * ppsec)) # + 0.5 1137 for i, lo, hi in izip(count(), lows[i0:i1], highs[i0:i1]): 1138 if gauss_intensity and (lo != hi): 1139 context.save() 1140 # position gauss at (p0+i, mean-5*std) where mean = (hi+lo)/2; std = (hi-lo)/2 1141 # scale so mean+5*std :: self.__gauss_surface.get_height() 1142 mean = (hi+lo)/2.0 1143 std = (hi-lo)/2.0 1144 gh = self.__gauss_surface.get_height() ### 1145 context.translate(p0+i, mean-5*std) 1146 context.scale(1, 10*std/gh) 1147 context.set_source_surface(self.__gauss_surface, 0, 0) 1148 context.rectangle(0, 0, 1, gh) 1149 context.fill() 1150 context.restore() 1151 1152 lo = hi = mean 1153 if lo != UNSET_IDL: 1154 context.move_to(p0+i, lo - units_per_pix) 1155 context.line_to(p0+i, hi + units_per_pix) 1156 context.stroke() 1157 context.restore()
1158 - def draw_color_idl(self, context, w, h, center, per_div, seg, lows, highs, class_bits, color_exc=qubx.scope.COLOR_EXCLUDED):
1159 if not numpy.any(class_bits): 1160 return 1161 dt = self.file.sampling 1162 halfwid = w / 2 1163 t_center, t_per_div = [self.signals.get(0, x) for x in ('Center', 'per div')] 1164 ppd_x, ppd_y = self.pix_per_div 1165 ppsec = ppd_x / t_per_div 1166 1167 context.save() 1168 context.translate(0, h/2) 1169 context.scale(1, -ppd_y / per_div) 1170 context.translate(0, -center) 1171 units_per_pix = per_div / ppd_y 1172 1173 context.set_line_width(self.appearance.line_width*self.appearance.emsize/8.0) 1174 chunks = seg.chunks = [seg] 1175 for chunk in chunks: 1176 if not chunk.included: 1177 context.set_source_rgba(*self.appearance.color(color_exc)) 1178 npix = len(lows) 1179 i0 = max(0, min(npix-1, int(round(npix*float(chunk.f -seg.f)/seg.n)))) 1180 i1 = max(0, min(npix, int(round(npix*float(chunk.l+1-seg.f)/seg.n)))) 1181 p0 = int(round((chunk.f - seg.f)*dt * ppsec)) # + 0.5 1182 for i, lo, hi, cb in izip(count(), lows[i0:i1], highs[i0:i1], class_bits[i0:i1]): 1183 if lo != UNSET_IDL: 1184 context.move_to(p0+i, lo - units_per_pix) 1185 context.line_to(p0+i, hi + units_per_pix) 1186 if chunk.included: 1187 for c in xrange(32): 1188 if (cb & (1 << c)): 1189 context.set_source_rgba(*SETALPHA(self.appearance.color(COLOR_CLASS(c)), 0.62)) 1190 context.stroke_preserve() 1191 context.new_path() 1192 else: 1193 context.stroke() 1194 context.restore()
1195 - def __onDrawGL(self, gldrawable, glcontext, w, h):
1196 if not self.file: return 1197 if not self.signals: return 1198 draw_bounds = self.draw_bounds.val 1199 if draw_bounds[0] == draw_bounds[1]: return 1200 t_per_div = self.signals.get(0, 'per div') 1201 if not t_per_div: return 1202 self.data_overlay.invalidate() 1203 self._drawing = True 1204 #dx = self.file.sampling * self.pix_per_div[0] / t_per_div # pixels per sample, with no downsampling 1205 #spp = 1.0 / dx 1206 spp = self.samp_per_pix.val 1207 dx = 1.0 / spp 1208 if spp >= 4.0: 1209 maxlen = int(round(max(spp, spp * int(round((1<<16)/spp))))) # adjust to even pixel bounds 1210 else: 1211 maxlen = (1<<16) 1212 signal_segs = [self.get_segmentation_screen(signal=i, baseline_nodes=self.subtract_baseline_nodes) for i in xrange(self.file.signals.size)] 1213 Nseg = len(signal_segs[0]) 1214 Nsigseg = 0 1215 signal_cpd = [ [self.signals.get(i+1, x) for x in ('Center', 'per div')] 1216 for i in xrange(self.file.signals.size) ] 1217 for s in xrange(self.file.signals.size): 1218 if self.file.analog[s-1] and (-SIGNAL_CENTER_HIDDEN != signal_cpd[s][0]): 1219 Nsigseg += Nseg 1220 for s, signals in enumerate( izip(*signal_segs) ): 1221 seg = signals[0] 1222 seg_n = seg.l - seg.f + 1 1223 seg_dur = seg_n * self.file.sampling 1224 seg_w = int(ceil(w * seg_dur / (draw_bounds[1] - draw_bounds[0]))) 1225 self.draw_list_gl(gldrawable, glcontext, w, h, self.file.list, seg, spp) 1226 #print 'making vv[',2*w,']',w 1227 vline_vertices = numpy.zeros(shape=(2*w, 2), dtype='float32') 1228 vline_vertices[::2,0] = numpy.arange(w, dtype='float32') 1229 vline_vertices[1::2,0] = vline_vertices[::2,0] 1230 for i, chunks in enumerate(self.file.analog[j] and generate_chunk_samples(seg.chunks, signal=j, get_excluded=True, downsample_w=w, downsample_Nsigseg=Nsigseg, maxlen=maxlen) or seg for j, seg in enumerate(signals)): 1231 center, per_div = signal_cpd[i] 1232 if SIGNAL_CENTER_HIDDEN != center: 1233 if chunks.next: # is iter of sampled chunks 1234 for chunk in chunks: 1235 samples, dt = chunk.samples, chunk.sampling 1236 sig_color = chunk.included and qubx.scope.COLOR_SIGNAL(self.signals.get(i+1, 'Name')) or qubx.scope.COLOR_EXCLUDED 1237 if seg_n < 4*w: 1238 self.draw_samples_gl(gldrawable, glcontext, w, h, center, per_div, sig_color, samples, dt, dx*(chunk.f-seg.f), seg_n) 1239 else: 1240 samples_per_pixel = self.signals.get(0,'per div') / (dt*self.pix_per_div[0]) 1241 resolution = per_div / self.pix_per_div[1] * self.appearance.emsize / 4.0 1242 i0 = max(0, min(seg_w-1, int(round(seg_w*float(chunk.f-seg.f)/seg_n)))) 1243 i1 = max(0, min(seg_w, int(round(seg_w*float(chunk.l-seg.f+1)/seg_n)))) 1244 lows, highs, gauss_los, gauss_his = qubx.fast.data.raster_samples(i1-i0, samples, samples_per_pixel, resolution) 1245 self.draw_samples_raster_gl(gldrawable, glcontext, w, h, center, per_div, sig_color, lows, highs, gauss_los, gauss_his, 1246 int(round(dx * (chunk.f-seg.f))), vline_vertices) 1247 self.draw_idl_gl(gldrawable, glcontext, w, h, center, per_div, i, chunk, chunk.f-seg.f, vline_vertices) 1248 else: 1249 self.draw_idl_gl(gldrawable, glcontext, w, h, center, per_div, i, chunks, 0, vline_vertices) 1250 self._drawing = False
1251 - def draw_list_gl(self, gldrawable, glcontext, w, h, lst, seg, spp):
1252 if not lst: return 1253 def rect(x0, y0, x1, y1, draw_type=OpenGL.GL.GL_QUADS): 1254 OpenGL.GL.glBegin(draw_type) 1255 OpenGL.GL.glVertex3f(x0, y0, 0) 1256 OpenGL.GL.glVertex3f(x0, y1, 0) 1257 OpenGL.GL.glVertex3f(x1, y1, 0) 1258 OpenGL.GL.glVertex3f(x1, y0, 0) 1259 OpenGL.GL.glEnd()
1260 # label y0 is .5em below scope controls 1261 sels = lst.selections_in(seg.f, seg.l, trimmed=True) 1262 for From,To,Label,Index in sels: 1263 g = lst[Index,'Group'] 1264 if g: 1265 OpenGL.GL.glColor4f(* SETALPHA(self.appearance.color(COLOR_CLASS(g)), COLOR_LIST_ALPHA)) 1266 else: 1267 OpenGL.GL.glColor4f(* self.appearance.color(COLOR_LIST)) 1268 px, px2 = [(i-seg.f)/spp for i in (From, To+1)] 1269 rect(px, 0, px2, h) 1270 if Index == self.list_sel: 1271 OpenGL.GL.glColor4f(*self.appearance.color(COLOR_LIST_SEL)) 1272 rect(px, 0, px2, h, OpenGL.GL.GL_LINE_STRIP) 1273 # text is a mess in opengl; could maybe do layers OnOverlay, or just make user click the beads along the top to read labels
1274 - def draw_samples_gl(self, gldrawable, glcontext, w, h, center, per_div, color, samples, dt, x0, seg_n):
1275 if not len(samples): return 1276 # samples start at draw_bounds[0] 1277 halfwid = w / 2 1278 t_center, t_per_div = [self.signals.get(0, x) for x in ('Center', 'per div')] 1279 ppd_x, ppd_y = self.pix_per_div 1280 ppsec = ppd_x / t_per_div 1281 dx = dt * ppd_x / t_per_div 1282 one = self.appearance.line_width * self.appearance.emsize/4.0 1283 # amp = h/2 + -(ppd_y / per_div) * (amp - center) 1284 samples -= center 1285 samples *= - ppd_y / per_div 1286 samples += h/2 1287 1288 color_tup = self.appearance.color(color) 1289 OpenGL.GL.glColor4f(* color_tup) 1290 OpenGL.GL.glLineWidth(one) 1291 OpenGL.GL.glEnable(OpenGL.GL.GL_LINE_SMOOTH) 1292 OpenGL.GL.glEnable(OpenGL.GL.GL_POLYGON_SMOOTH) 1293 one = self.appearance.emsize/4.0 1294 1295 vv = numpy.zeros(shape=(len(samples),2), dtype='float32') 1296 vv[:,0] = numpy.arange(len(samples), dtype='float32') 1297 vv[:,0] *= dx 1298 vv[:,0] += x0 1299 vv[:,1] = samples 1300 OpenGL.GL.glVertexPointerf(vv) 1301 OpenGL.GL.glEnableClientState(OpenGL.GL.GL_VERTEX_ARRAY) 1302 OpenGL.GL.glDrawArrays(OpenGL.GL.GL_LINE_STRIP, 0, len(samples)) 1303 if seg_n < (w/4): 1304 pvv = numpy.zeros(shape=(DOT_DIVISIONS,2), dtype='float32') 1305 angle = numpy.arange(DOT_DIVISIONS, dtype='float32') * (360.0 / DOT_DIVISIONS) 1306 pvv[:,0] = numpy.sin(angle) 1307 pvv[:,1] = numpy.cos(angle) 1308 pvv *= 1.5*one 1309 OpenGL.GL.glVertexPointerf(pvv) 1310 for i in xrange(len(samples)): 1311 OpenGL.GL.glPushMatrix() 1312 OpenGL.GL.glTranslatef(vv[i,0], vv[i,1], 0.0) 1313 OpenGL.GL.glDrawArrays(OpenGL.GL.GL_TRIANGLE_FAN, 0, DOT_DIVISIONS) 1314 OpenGL.GL.glPopMatrix() 1315 OpenGL.GL.glDisableClientState(OpenGL.GL.GL_VERTEX_ARRAY) 1316 OpenGL.GL.glDisable(OpenGL.GL.GL_LINE_SMOOTH) 1317 OpenGL.GL.glDisable(OpenGL.GL.GL_POLYGON_SMOOTH)
1318 - def draw_samples_raster_gl(self, gldrawable, glcontext, w, h, center, per_div, sig_color, lows, highs, gauss_los, gauss_his, pixels_from_start, vv):
1319 color_tup = self.appearance.color(sig_color) 1320 OpenGL.GL.glPushMatrix() 1321 OpenGL.GL.glTranslate(0.0, h/2.0, 0.0) 1322 OpenGL.GL.glScale(1.0, - self.pix_per_div[1] / per_div, 1.0) 1323 OpenGL.GL.glTranslate(0.0, - center, 0.0) 1324 1325 OpenGL.GL.glEnableClientState(OpenGL.GL.GL_VERTEX_ARRAY) 1326 OpenGL.GL.glVertexPointerf(vv) 1327 OpenGL.GL.glLineWidth(1.0) 1328 1329 cPartial = qubx.toolspace.SETALPHA(color_tup, .5*color_tup[3]) 1330 1331 N = min(len(lows), (vv.shape[0] - 2*pixels_from_start)/2) 1332 vv[2*pixels_from_start:2*(pixels_from_start+N):2,1] = lows[:N] 1333 vv[2*pixels_from_start+1:2*(pixels_from_start+N):2,1] = highs[:N] 1334 OpenGL.GL.glColor4f(*cPartial) 1335 OpenGL.GL.glDrawArrays(OpenGL.GL.GL_LINES, 2*pixels_from_start, 2*N) 1336 1337 vv[2*pixels_from_start:2*(pixels_from_start+N):2,1] = gauss_los[:N] 1338 vv[2*pixels_from_start+1:2*(pixels_from_start+N):2,1] = gauss_his[:N] 1339 OpenGL.GL.glColor4f(*color_tup) 1340 OpenGL.GL.glDrawArrays(OpenGL.GL.GL_LINES, 2*pixels_from_start, 2*N) 1341 OpenGL.GL.glPopMatrix()
1342 - def draw_idl_gl(self, gldrawable, glcontext, w, h, center, per_div, sig, seg, offset, vv):
1343 t_center, t_per_div = [self.signals.get(0, x) for x in ('Center', 'per div')] 1344 t_left = t_center - self.divs[0] * t_per_div / 2 1345 ppd_x, ppd_y = self.pix_per_div 1346 ppsec = ppd_x / t_per_div 1347 seg_n = seg.l - seg.f + 1 1348 seg_dur = seg_n * seg.file.sampling 1349 seg_w = min(w, int(ceil(seg_dur * ppsec))) 1350 seg_index = self.file.segmentation.index_at(seg.f) 1351 lows, highs, class_bits = self.get_sampled_events(sig, seg_index, seg.f, seg.l, seg_w) 1352 OpenGL.GL.glPushMatrix() 1353 xoff = int(round(seg.file.sampling * offset * ppsec)) 1354 OpenGL.GL.glTranslatef(xoff, 0, 0) 1355 self.draw_sampled_idl_gl(gldrawable, glcontext, seg_w, h, xoff, center, per_div, seg, lows, highs, vv) 1356 OpenGL.GL.glPopMatrix()
1357 - def draw_sampled_idl_gl(self, gldrawable, glcontext, w, h, xoff, center, per_div, seg, lows, highs, vv, color_inc=qubx.scope.COLOR_IDEALIZATION, color_exc=qubx.scope.COLOR_EXCLUDED):
1358 if not numpy.any(lows != UNSET_IDL): 1359 return 1360 dt = self.file.sampling 1361 halfwid = w / 2 1362 t_center, t_per_div = [self.signals.get(0, x) for x in ('Center', 'per div')] 1363 ppd_x, ppd_y = self.pix_per_div 1364 ppsec = ppd_x / t_per_div 1365 1366 OpenGL.GL.glPushMatrix() 1367 OpenGL.GL.glTranslate(0.0, h/2, 0.0) 1368 OpenGL.GL.glScale(1.0, - ppd_y / per_div, 1.0) 1369 OpenGL.GL.glTranslate(0.0, - center, 0.0) 1370 units_per_pix = per_div / ppd_y 1371 1372 OpenGL.GL.glEnableClientState(OpenGL.GL.GL_VERTEX_ARRAY) 1373 OpenGL.GL.glVertexPointerf(vv) 1374 OpenGL.GL.glLineWidth(self.appearance.line_width*self.appearance.emsize/8.0) 1375 # OpenGL.GL.glEnable(OpenGL.GL.GL_LINE_SMOOTH) 1376 1377 chunks = seg.chunks = [seg] 1378 for chunk in chunks: 1379 if chunk.included: 1380 OpenGL.GL.glColor4f(*self.appearance.color(color_inc)) 1381 else: 1382 OpenGL.GL.glColor4f(*self.appearance.color(color_exc)) 1383 npix = len(lows) 1384 i0 = max(0, min(npix-1, int(round(npix*float(chunk.f -seg.f)/seg.n)))) 1385 i1 = max(0, min(npix, int(round(npix*float(chunk.l+1-seg.f)/seg.n)))) 1386 p0 = int(round((chunk.f - seg.f)*dt * ppsec)) # + 0.5 1387 Nhere = i1 - i0 1388 vv[2*p0:2*(p0+Nhere):2,1] = lows[i0:i1] - units_per_pix 1389 vv[2*p0+1:2*(p0+Nhere):2,1] = highs[i0:i1] + units_per_pix 1390 OpenGL.GL.glDrawArrays(OpenGL.GL.GL_LINES, 2*p0, 2*Nhere) 1391 OpenGL.GL.glDisableClientState(OpenGL.GL.GL_VERTEX_ARRAY) 1392 OpenGL.GL.glPopMatrix()
1393 - def get_sampled_events(self, sig, seg_index, f, l, Nsample, unset_val=UNSET_IDL):
1394 """Returns numpy.arrays (lows, highs, class_bits) with min and max idealized amp for each sample. 1395 1396 @param sig: signal index 1397 @param seg_index: index in segmentation containing f and l 1398 @param f: start sample index 1399 @param l: end sample index, inclusive 1400 @param Nsample: screen width, usually 1401 @param unset_val: samples with no events are initialized to this value 1402 """ 1403 lows = numpy.zeros(shape=(Nsample,), dtype='float32') 1404 highs = numpy.zeros(shape=(Nsample,), dtype='float32') 1405 class_bits = numpy.zeros(shape=(Nsample,), dtype='uint32') 1406 lows += unset_val 1407 highs += unset_val 1408 amps = numpy.array(self.file.ideal[sig].seg[seg_index].amp, dtype='float64') 1409 self.file.ideal[sig].idl.sample_dwells(f, l, amps, Nsample, lows, highs, class_bits) 1410 return lows, highs, class_bits
1411 - def get_sampled_fits(self, sig, seg_index, f, l, Nsample, unset_val=UNSET_IDL):
1412 """Returns numpy.arrays (lows, highs) with min and max idealized amp for each sample. 1413 1414 @param sig: signal index 1415 @param seg_index: index in segmentation containing f and l 1416 @param f: start sample index 1417 @param l: end sample index, inclusive 1418 @param Nsample: screen width, usually 1419 @param unset_val: samples with no events are initialized to this value 1420 """ 1421 lows = numpy.zeros(shape=(Nsample,), dtype='float32') 1422 highs = numpy.zeros(shape=(Nsample,), dtype='float32') 1423 lows += unset_val 1424 highs += unset_val 1425 self.file.fits[sig].idl.sample_dwells_std(f, l, self.file.fits[sig].idl.segmeans[seg_index], self.file.fits[sig].idl.segstds[seg_index], Nsample, lows, highs) 1426 return lows, highs
1427 - def get_center_sample(self):
1428 """Returns the index of the sample under the cross-hairs.""" ### TODO: override in lo-res to ref. hi-res if possible 1429 center = self.signals.get(0, 'Center') 1430 halfsamp = self.file.sampling / 2 1431 segm = self.get_segmentation(left=center-halfsamp, right=center+.999*halfsamp, source=DATASOURCE_SCREEN) 1432 return segm[0].chunks[0].l
1433 - def get_segmentation_file(self, signal=None, latency=None, screen_sel=False, baseline_nodes=True):
1434 """Returns a list of L{SourceSeg} corresponding to the whole file.""" 1435 left = right = None 1436 sig = qubx.global_namespace.QubX.DataSource.signal if (signal is None) else signal 1437 if screen_sel: 1438 center, per_div = [self.signals[0,x] for x in ('Center', 'per div')] 1439 halfw = per_div * self.divs[0] 1440 left, right = center-halfw, center+halfw 1441 return self.file.get_segmentation(left=left, right=right, signal=sig, latency=latency, baseline_nodes=baseline_nodes)
1442 - def get_segmentation_screen(self, signal=None, line=None, latency=None, baseline_nodes=True):
1443 """Returns a list of L{SourceSeg} corresponding to what's onscreen.""" 1444 draw_bounds = self.draw_bounds.val 1445 sig = qubx.global_namespace.QubX.DataSource.signal if (signal is None) else signal 1446 if not (line is None): 1447 left = draw_bounds[0] 1448 line_width = self.divs[0]*self.signals[0, 'per div'] 1449 draw_bounds = [left + line*line_width, left + (line+1)*line_width] 1450 return self.file.get_segmentation(self.time.Iseg, self.time.Iseg + self.time.Greal - 1, 1451 draw_bounds[0], draw_bounds[1], signal=sig, latency=latency, baseline_nodes=baseline_nodes)
1452 - def get_segmentation(self, first_seg=None, last_seg=None, left=None, right=None, source=DATASOURCE_FILE, signal=None, latency=None, baseline_nodes=True):
1453 """Returns a list of L{SourceSeg} corresponding to source, modified by args. 1454 1455 @param first_seg: 1456 @param last_seg: 1457 @param left: time offset from start-of-each-seg 1458 @param right: time offset from start-of-each-seg 1459 @param source: DATASOURCE_FILE, DATASOURCE_SCREEN (overridden by other options), or DATASOURCE_LIST 1460 @param signal: index in file.signals (default QubX.DataSource.signal) 1461 @param latency: number of samples to shift the data rightwards (same f, l, and idealization) 1462 """ 1463 sig = qubx.global_namespace.QubX.DataSource.signal if (signal is None) else signal 1464 segf, segl, l, r = first_seg, last_seg, left, right 1465 if source == DATASOURCE_SCREEN: 1466 if segf is None: 1467 segf = self.time.Iseg 1468 if segl is None: 1469 segl = segf + self.time.Greal - 1 1470 if l is None: 1471 l = self.time.sel_left if (self.time.sel_left != self.time.sel_right) else self.time.left # draw_bounds[0] 1472 if r is None: 1473 r = self.time.sel_right if (self.time.sel_left != self.time.sel_right) else self.time.right # draw_bounds[1] 1474 return self.file.get_segmentation(segf, segl, l, r, sig, latency, baseline_nodes=baseline_nodes)
1475 - def get_segmentation_indexed(self, first, last, signal=0, latency=None, baseline_nodes=True):
1476 sig = qubx.global_namespace.QubX.DataSource.signal if (signal is None) else signal 1477 return self.file.get_segmentation_indexed(first, last, sig, latency, baseline_nodes=baseline_nodes)
1478 - def get_segmentation_list(self, list_name=None, signal=None, baseline_nodes=True, group=None):
1479 sig = qubx.global_namespace.QubX.DataSource.signal if (signal is None) else signal 1480 return self.file.get_segmentation_list(list_name=list_name, signal=sig, baseline_nodes=baseline_nodes, group=group)
1481 - def get_sels_screen(self, left=None, right=None, hilite=False):
1482 segs = self.get_segmentation(left=left, right=right, source=DATASOURCE_SCREEN) 1483 return [(seg.f, seg.l) for seg in segs]
1484 - def add_baseline_node_between(self, left=None, right=None):
1485 center = self.signals[0, 'Center'] 1486 per_div = self.signals[0, 'per div'] 1487 seg = self.get_segmentation(source=DATASOURCE_SCREEN, left=left, right=right, signal=qubx.global_namespace.QubX.DataSource.signal, baseline_nodes=False)[0] 1488 mid = (seg.f + seg.l) / 2 1489 val = seg.get_samples().samples.mean() 1490 qubx.pyenv.env.OnScriptable('QubX.Data.file.baseline[QubX.DataSource.signal].add_node(%i, %s)' % (mid, repr(val))) 1491 self.file.baseline[seg.signal].add_node(mid, val)
1492 1493 1494 fields = property(lambda self: self.nb_get_headers(), doc="for use with Notebook_Table_Extensions")
1495 - def add_notebook_item(self, name, item):
1496 if name in self.notebook: 1497 self.subNotebook.items.remove(self.notebook[name]) 1498 self.notebook[name] = item 1499 self.subNotebook.items.append(item)
1500 - def nb_get_shape(self):
1501 return self.dim
1502 - def nb_draw(self, context, w, h):
1503 self.draw_to_context(context, w, h, overlay=True)
1504 - def nb_draw_no_overlays(self, context, w, h):
1505 self.draw_to_context(context, w, h, overlay=False)
1506 # to table ### TODO: segments in columns (what about non-matching excluded regions?)
1507 - def nb_get_caption(self):
1508 return os.path.split(self.file.path)[1] or "[Data excerpt]"
1509 - def nb_iter_table_cols(self):
1510 if not self.file.signals: 1511 return 1512 for i in xrange(self.file.signals.size): 1513 yield i, False, False 1514 for seg in self.get_segmentation_screen(i): 1515 for chunk in seg.chunks: 1516 if chunk.included: 1517 ff, ll, cc = self.file.fits[i].idl.get_dwells(seg.f, seg.l, True, False) 1518 if numpy.any(cc > -1): 1519 yield i, True, False 1520 if numpy.any(self.file.fits[i].idl.segstds[seg.index]): 1521 yield i, False, True 1522 break
1523 - def nb_get_table_shape(self):
1524 segs = self.get_segmentation_screen() 1525 n = 0 1526 if len(segs) == 1: 1527 for chunk in segs[0].chunks: 1528 if chunk.included: 1529 n += chunk.n 1530 else: 1531 for seg in segs: 1532 if seg.n > n: 1533 n = seg.n 1534 cols = [(index, is_fit, is_fit_std) for index, is_fit, is_fit_std in self.nb_iter_table_cols()] 1535 return (n, 1+len(segs)*len(cols))
1536 - def nb_get_headers(self):
1537 hdrs = ['Time'] 1538 cols = [(index, is_fit, is_fit_std) for index, is_fit, is_fit_std in self.nb_iter_table_cols()] 1539 segs = self.get_segmentation_screen() 1540 if len(segs) > 1: 1541 segix = lambda seg: "_%d"%(seg.index+1) 1542 else: 1543 segix = lambda seg: "" 1544 for seg in segs: 1545 for index, is_fit, is_fit_std in cols: 1546 hdrs.append("%s%s%s" % (self.signals.get(index+1, 'Name'), 1547 (is_fit and "_fit") or (is_fit_std and "_fit_std") or "", 1548 segix(seg))) 1549 return hdrs
1550 - def nb_get_col(self, c):
1551 Nr, Nc = self.nb_get_table_shape() 1552 pieces = [] 1553 if c == 0: 1554 segs = self.get_segmentation_screen() 1555 if len(segs) > 1: 1556 arr = numpy.arange(Nr, dtype='float32') 1557 arr += (segs[0].f - segs[0].offset) 1558 arr *= self.file.sampling 1559 pieces.append(arr) 1560 else: 1561 seg = segs[0] 1562 for chunk in seg.chunks: 1563 if chunk.included: 1564 arr = numpy.arange(chunk.n, dtype='float32') 1565 arr += (chunk.f - seg.f) 1566 arr *= self.file.sampling 1567 arr += (seg.start - seg.file.segments[0, 'Start']) * 1e-3 1568 pieces.append(arr) 1569 else: 1570 cols = [(index, is_fit, is_fit_std) for index, is_fit, is_fit_std in self.nb_iter_table_cols()] 1571 index, is_fit, is_fit_std = cols[(c-1)%len(cols)] 1572 segs = self.get_segmentation_screen(index) # correct filter, latency etc. 1573 seg = segs[(c-1)/len(cols)] 1574 for chunk in seg.chunks: 1575 if chunk.included: 1576 if is_fit: 1577 arr = numpy.zeros(shape=(chunk.n,), dtype='float32') 1578 self.file.fits[index].idl.get_samples_into(chunk.f, chunk.l, arr) 1579 pieces.append(arr) 1580 elif is_fit_std: 1581 arr = numpy.zeros(shape=(chunk.n,), dtype='float32') 1582 arr_std = numpy.zeros(shape=(chunk.n,), dtype='float32') 1583 self.file.fits[index].idl.get_samples_into(chunk.f, chunk.l, arr, arr_stds=arr_std) 1584 pieces.append(arr_std) 1585 else: 1586 pieces.append(get_chunk_samples(chunk).samples) 1587 elif len(segs) > 1: 1588 pieces.append(numpy.zeros(shape=(chunk.n,), dtype='float32')) 1589 return numpy.hstack(pieces)
1590 - def nb_get_col_format(self, c):
1591 return lambda x: '%.10g' % x
1592 - def nb_get_trace_xlabel(self):
1593 return "Time [s]"
1594 - def nb_get_trace_ylabel(self):
1595 units = self.signals.get(self.QubX.DataSource.signal+1, 'Units') 1596 if units: 1597 units = ' [%s]' % units 1598 return self.signals.get(self.QubX.DataSource.signal+1, 'Name')+units
1599 - def nb_get_trace_series(self, all=False):
1600 series = [] 1601 for c, ixfit in enumerate(self.nb_iter_table_cols()): 1602 index, is_fit, is_fit_std = ixfit 1603 if (index == self.QubX.DataSource.signal) or (all and (self.signals[index+1,'Center'] != SIGNAL_CENTER_HIDDEN)): 1604 if is_fit: 1605 colorref = COLOR_FITS 1606 elif not is_fit_std: 1607 colorref = qubx.scope.COLOR_SIGNAL(self.signals.get(index+1, 'Name')) 1608 series.append(qubx.notebook.NbChartSeries(0, c+1, qubx.notebook.LINES, color_rgb=self.appearance.color(colorref))) 1609 return series
1610 - def nb_get_trace_series_all(self):
1611 return self.nb_get_trace_series(all=True)
1612 - def nb_get_caption_list(self):
1613 return self.file.list.list_name
1614 - def nb_get_table_shape_list(self, segs=None):
1615 n = 0 1616 ss = segs or self.get_segmentation_list() 1617 for seg in ss: 1618 n = max(n, seg.n) 1619 return (n, 1+len(ss))
1620 - def nb_get_headers_list(self):
1621 hdrs = ['Time'] 1622 lst = self.file.list 1623 for i in xrange(lst.size): 1624 hdrs.append(lst.get(i, 'Label') or str(i)) 1625 return hdrs
1626 - def nb_get_col_list(self, c):
1627 segs = self.get_segmentation_list() 1628 Nr, Nc = self.nb_get_table_shape_list(segs) 1629 if c == 0 : 1630 arr = numpy.arange(Nr, dtype='float32') 1631 arr *= self.file.sampling 1632 return arr 1633 else: 1634 arr = numpy.zeros(Nr, dtype='float32') 1635 arr[:segs[c-1].n] = segs[c-1].get_samples().samples 1636 return arr
1637 - def nb_get_trace_series_list(self, all=False):
1638 return [qubx.notebook.NbChartSeries(0, c, qubx.notebook.LINES, color_rgb=(0,0,0)) for c in xrange(1, self.file.list.size+1)]
1639
1640 1641 1642 #DOWNSAMPLE_ALL_LOGCOUNT = 2.0 1643 #DOWNSAMPLE_LOGSLOPE = 3.0 1644 #DOWNSAMPLES_PER_PIX = lambda Nsigseg, Nsamp: exp( (log(Nsigseg*Nsamp) - DOWNSAMPLE_ALL_LOGCOUNT) / DOWNSAMPLE_LOGSLOPE + DOWNSAMPLE_ALL_LOGCOUNT ) 1645 1646 -class CacheTile(object):
1647 - def __init__(self, width=1024):
1648 self.__cap = self.__width = 0 1649 self.i_signal = self.i_seg = self.i_left = self.i_right = -1 1650 self.width = width 1651 self.serial = -1
1652 width = property(lambda self: self.__width, lambda self, x: self.set_width(x))
1653 - def set_width(self, x):
1654 if x > self.__cap: 1655 cap = self.__cap = max(x, 2*self.__cap) 1656 self.lo = numpy.zeros(dtype='int16', shape=(cap,)) 1657 self.hi = numpy.zeros(dtype='int16', shape=(cap,)) 1658 self.minus_s = numpy.zeros(dtype='int16', shape=(cap,)) 1659 self.plus_s = numpy.zeros(dtype='int16', shape=(cap,)) 1660 self.__width = x
1661 - def load(self, view, i_signal, i_seg, i_left, i_right, center, per_div, samp_per_pix, Nsigseg=1, baseline_nodes=True):
1662 #print 'load',view, i_signal, i_seg, i_left, i_right, center, per_div, samp_per_pix, Nsigseg 1663 self.i_signal = i_signal 1664 self.i_seg = i_seg 1665 self.i_left = i_left 1666 self.i_right = i_right 1667 if not per_div: 1668 return 1669 Nsamp = i_right - i_left 1670 seg_left = view.file.segmentation.segments[i_seg][0] 1671 #print seg_left 1672 segs = view.get_segmentation_indexed(seg_left+i_left, seg_left+i_right-1, i_signal, baseline_nodes=baseline_nodes) 1673 if not segs: return 0 1674 seg = segs[0] 1675 actual_spp = Nsamp * 1.0 / max(self.__width, 1) 1676 samples = seg.get_samples().samples 1677 resolution = per_div / view.pix_per_div[1] * view.appearance.emsize / 4.0 1678 lows, highs, gauss_los, gauss_his = qubx.fast.data.raster_samples(self.__width, samples, samp_per_pix, resolution) 1679 for arr, dest in [(lows, self.lo), (highs, self.hi), (gauss_los, self.minus_s), (gauss_his, self.plus_s)]: 1680 arr -= center 1681 arr *= - view.pix_per_div[1] / per_div 1682 arr += view._dim[1] / 2 1683 dest[:min(self.__width, len(arr))] = arr 1684 return len(lows)
1685
1686 -class TileCache(object):
1687 - def __init__(self, i_signal, tiles_per_seg=3, tile_width=1024):
1688 self.__i_signal = i_signal 1689 self.__tiles_per_seg = tiles_per_seg 1690 self.__tile_width = tile_width 1691 self.__samp_per_pix = 1.0 1692 self.center = 1.0 1693 self.per_div = 1.0 1694 self.__last_segb = (-1, -1) 1695 self.__Nseg = 1 1696 self.serial = 0 1697 self.__baseline_nodes = True 1698 self.active_heap = [] 1699 self.spare_list = [] 1700 self.tile_map = {} # {(i_seg, i_left) : tile} 1701 self.__adjust_seg_tiles()
1702 samp_per_pix = property(lambda self: self.__samp_per_pix, lambda self, x: self.set_samp_per_pix(x))
1703 - def set_samp_per_pix(self, x):
1704 if self.__samp_per_pix == x: return 1705 self.__samp_per_pix = x 1706 self.clear()
1707 i_signal = property(lambda self: self.__i_signal, lambda self, x: self.set_i_signal(x))
1708 - def set_i_signal(self, x):
1709 if self.__i_signal == x: return 1710 self.__i_signal = x 1711 self.clear()
1712 tiles_per_seg = property(lambda self: self.__tiles_per_seg, lambda self, x: self.set_tiles_per_seg(x))
1713 - def set_tiles_per_seg(self, x):
1714 if self.__tiles_per_seg == x: return 1715 self.__tiles_per_seg = x 1716 self.__adjust_seg_tiles()
1717 tile_width = property(lambda self: self.__tile_width, lambda self, x: self.set_tile_width(x))
1718 - def set_tile_width(self, x):
1719 if self.__tile_width == x: return 1720 self.__tile_width = x 1721 self.clear() 1722 for tile in self.spare_list: 1723 tile.width = x
1724 - def __prep_tile(self):
1725 if self.spare_list: 1726 tile = self.spare_list[-1] 1727 del self.spare_list[-1] 1728 #print 'using spare' 1729 else: 1730 serial, tile = heapq.heappop(self.active_heap) 1731 del self.tile_map[(tile.i_seg, tile.i_left)] 1732 #print 'reusing',(tile.i_seg, tile.i_left) 1733 return self.__heappush_tile(tile)
1734 - def __update_tile(self, tile):
1735 #print 'already loaded',(tile.i_seg, tile.i_left) 1736 self.__expire_tile(tile, False) 1737 return self.__heappush_tile(tile)
1738 - def __expire_tile(self, tile, unmap=True):
1739 tile.serial = -1 1740 for i in reversed(xrange(len(self.active_heap))): 1741 if self.active_heap[i][1] == tile: 1742 heapq_remove(self.active_heap, i) 1743 if unmap: 1744 del self.tile_map[(tile.i_seg, tile.i_left)] 1745 #print 'expiring',(tile.i_seg, tile.i_left) 1746 break
1747 - def __heappush_tile(self, tile):
1748 tile.serial = self.serial 1749 self.serial += 1 1750 heapq.heappush(self.active_heap, (tile.serial, tile)) 1751 return tile
1752 - def clear(self):
1753 #print 'clearing' 1754 for serial, tile in self.active_heap: 1755 tile.serial = -1 1756 self.spare_list.append(tile) 1757 self.active_heap[:] = [] 1758 self.tile_map.clear() 1759 self.serial = 0
1760 - def set_segs_onscreen(self, Iseg, Greal):
1761 if self.__last_segb == (Iseg, Greal): 1762 return 1763 self.__last_segb = (Iseg, Greal) 1764 self.__Nseg = Greal 1765 # expire any tile with not (Iseg <= i_seg < (Iseg+Greal)) 1766 for tile in [tile for serial, tile in self.active_heap]: 1767 if not (Iseg <= tile.i_seg < (Iseg + Greal)): 1768 self.__expire_tile(tile) 1769 self.__adjust_seg_tiles()
1770 - def __adjust_seg_tiles(self):
1771 # expire old tiles until len(unexpired) <= (tiles_per_seg * Nseg) 1772 while len(self.active_heap) > (self.__tiles_per_seg * self.__Nseg): 1773 serial, tile = heapq.heappop(self.active_heap) 1774 del self.tile_map[(tile.i_seg, tile.i_left)] 1775 self.spare_list.append(tile) 1776 # grow/shrink spares to meet (tiles_per_seg * Nseg - len(unexpired)) 1777 Nspare = self.__tiles_per_seg*self.__Nseg - len(self.active_heap) 1778 while len(self.spare_list) < Nspare: 1779 self.spare_list.append(CacheTile(self.__tile_width)) 1780 if len(self.spare_list) > Nspare: 1781 del self.spare_list[Nspare:]
1782 - def __tile_coords(self, i_in_seg):
1783 """Returns tile.i_left, i_in_tile.""" 1784 tile_samples = int(round(self.__tile_width * self.__samp_per_pix)) 1785 return (tile_samples * (i_in_seg / tile_samples), int(round((i_in_seg % tile_samples) * 1.0 / self.__samp_per_pix)))
1786 - def get_tile(self, view, i_seg, i_left, Nsigseg=1, baseline_nodes=True):
1787 #print 'get_tile',view, i_seg, i_left, Nsigseg 1788 if (i_seg, i_left) in self.tile_map: 1789 return self.__update_tile(self.tile_map[(i_seg, i_left)]) 1790 tile_samples = int(round(self.__tile_width * self.__samp_per_pix)) 1791 # print 'cache miss:', i_seg, i_left 1792 tile = self.__prep_tile() 1793 tile.actual_n = tile.load(view, self.i_signal, i_seg, i_left, i_left+tile_samples, self.center, self.per_div, self.__samp_per_pix, Nsigseg, baseline_nodes) 1794 self.tile_map[(i_seg, i_left)] = tile 1795 return tile
1796 - def read(self, view, i_seg, i_left, lo, hi, gauss_lo, gauss_hi, Nsigseg=1, baseline_nodes=True):
1797 # get, load tiles overlapping i_left..i_right, copy into lo, hi, gauss_lo, gauss_hi 1798 #print 'read',view, i_seg, i_left, lo, hi, gauss_lo, gauss_hi, Nsigseg 1799 #print self.__tile_width, self.__samp_per_pix 1800 if baseline_nodes != self.__baseline_nodes: 1801 self.__baseline_nodes = baseline_nodes 1802 self.clear() 1803 tile_samples = int(round(self.__tile_width * self.__samp_per_pix)) 1804 tile_left, tile_i = self.__tile_coords(i_left) 1805 at = 0 1806 while at < self.__tile_width: # 1 or 2 times, since read width == tile width 1807 tile = self.get_tile(view, i_seg, tile_left, Nsigseg, baseline_nodes) 1808 #print at, tile_i, tile.actual_n 1809 tile_n = min(self.__tile_width - at, tile.actual_n - tile_i) ### tile.actual_n - max(at, tile_i) ### 1810 if tile_n <= 0: 1811 break 1812 #print at, tile_n, tile.actual_n, self.__tile_width 1813 #print 1814 for src, dest in [(tile.lo, lo), (tile.hi, hi), (tile.minus_s, gauss_lo), (tile.plus_s, gauss_hi)]: 1815 dest[at:at+tile_n] = src[tile_i:tile_i+tile_n] 1816 at += tile_n 1817 if tile.actual_n < self.__tile_width: 1818 break 1819 tile_left += tile_samples 1820 tile_i = 0 1821 #print 'read %i pixels' % at 1822 return at
1823
1824 1825 1826 1827 1828 -class QubDataView_Lo(QubDataView_Base):
1829 """Low-resolution data view 1830 1831 @ivar time: L{TimeControlLayer} 1832 @ivar layTools: L{qubx.toolspace.Layer_Toolbar} 1833 @ivar tool_zoom: zoom-in tool 1834 @ivar tool_ruler: measurement tool 1835 """ 1836 1837 __explore_featured = ['tool_sel', 'layDisplay', 'subDisplay', 'layZoom', 'hires', 1838 'do_auto_scale', 'show_list_item', 'zoom_in', 'zoom_in_at', 1839 'get_segmentation_hires', 'measure', 'measure_list', 'measure_segments', 'measure_datasource'] 1840
1841 - def __init__(self, datas_table, menu_width):
1842 """ 1843 @param datas_table: the L{qubx.data_types.QubDatas} table of open data files 1844 @param menu_width: width of the open-files menu, in widths of 'M' 1845 """ 1846 QubDataView_Base.__init__(self, 'QubX.Data.view', True) 1847 self.__ref = Reffer() 1848 self.layNotebook.x = menu_width+5 1849 self.notebook['Pick'] = qubx.notebookGTK.NbSubTablePicker(self, "%s.notebook['Pick']"%self.global_name, self.nb_get_caption, NbSubTable='qubx.dataGTK.NbSubTrace') 1850 self.subNotebook.items.append(self.notebook['Pick']) 1851 1852 self.__serial_auto_scale = 0 1853 1854 self.signals.OnAfterInsert += self.__ref(self.__onAfterInsertSignal) 1855 self.signals.OnSet += self.__ref(self.__onSetSignal) 1856 self.__ignore_evt = False 1857 self.layNotebook.x = menu_width + 5 1858 1859 self.layDisplay = qubx.toolspace.Layer(menu_width+7.75, -3.5, 2.5, 2.5, qubx.toolspace.COLOR_CLEAR) 1860 self.controls.add_layer(self.layDisplay) 1861 self.subDisplay = SubLayer_DisplayMenu() 1862 self.subDisplay.tooltip = 'Display menu' 1863 self.layDisplay.add_sublayer(self.subDisplay) 1864 1865 self.time = TimeControlLayer(self.signals, menu_width+11) 1866 self.time.OnChangeShowing += self.__ref(self.__onChangeShowing) 1867 self.time.OnChangeSel += self.__ref(self.__onChangeSel) 1868 self.controls.add_layer(self.time) 1869 1870 self.layZoom = qubx.toolspace.Layer(x=-3.5, y=2, w=3, h=1.5, cBG=COLOR_ZOOM_BG) 1871 self.controls.add_layer(self.layZoom) 1872 self.subZoomOut = qubx.toolspace.SubLayer_SmoothZoom('-', zoom_out=True, x=0, y=0, w=1.5, h=1.5) 1873 self.subZoomOut.OnZoom += self.__ref(self.__onZoom) 1874 self.layZoom.add_sublayer(self.subZoomOut) 1875 self.subZoomIn = qubx.toolspace.SubLayer_SmoothZoom('+', x=1.5, y=0, w=1.5, h=1.5) 1876 self.subZoomIn.OnZoom += self.__ref(self.__onZoom) 1877 self.layZoom.add_sublayer(self.subZoomIn) 1878 1879 self.hires = QubDataView_Hi(self.signals, self.time) 1880 # these into hires, but need to be in lores as well for script compatibility: 1881 self.request_fit_controls = lambda show_fit: self.hires.request_fit_controls(show_fit) 1882 self.tool_ruler = self.hires.tool_ruler 1883 self.tool_baseline = self.hires.tool_baseline 1884 1885 self.tool_sel = QubData_SelTool(action=self.__ref(self.__onSel), dblclick=self.__ref(self.__onDblClick), 1886 scroll=self.__ref(self.__onScroll), hires_keys=False) 1887 self.tool = self.tool_sel
1888
1889 - def dispose(self):
1890 QubDataView_Base.dispose(self) 1891 self.__ref.clear()
1892
1893 - def compute_divs(self, w, h):
1894 hi_lt, hi_tp, hi_w, hi_h = self.hires.get_allocation() 1895 hi_x, hi_y = self.hires.compute_divs(hi_w, hi_h) 1896 dx, dy = qubx.scope.divs_for_pix(w), qubx.scope.divs_for_pix(h, LORES_PIX_PER_DIV) 1897 line_count = max(1, int(ceil(dy * 1.0 / hi_y))) if self.multi_line else 1 1898 #print 'Ndiv <-', (dx * self.line_count),"center=",self.signals[0, 'Center'],", perdiv=",self.signals[0, 'per div'] 1899 # if line_count increases, it should be set before Ndiv (first the divs zoom in, then their number increases, and at all times there is enough data to fill them) 1900 if line_count > self.line_count: 1901 self.line_count = line_count 1902 self.time.Ndiv = dx * line_count 1903 else: # and vice versa (fewer lines -> fewer divs -> longer divs) 1904 self.time.Ndiv = dx * line_count 1905 self.line_count = line_count 1906 #print h, dy, hi_h, hi_y, self.line_count 1907 return dx, hi_y
1908 - def set_line_count(self, x):
1909 if self.signals is None: 1910 return 1911 prev = self.line_count 1912 if prev != x: 1913 QubDataView_Base.set_line_count(self, x) 1914 #print prev, x, self.signals[0, 'per div'], 1915 self.signals[0, 'per div'] = self.signals[0, 'per div'] * (prev * 1.0 / x) 1916 #print self.signals[0, 'per div'] 1917 for i in xrange(1, len(self.signals)): 1918 if self.signals[i, 'Center'] == SIGNAL_CENTER_HIDDEN: 1919 self.tile_cache[i-1].tiles_per_seg = 0 1920 else: 1921 self.tile_cache[i-1].tiles_per_seg = 3*self.line_count 1922 if prev == 1: 1923 self.controls.remove_layer(self.layLists) 1924 elif x == 1: 1925 self.controls.add_layer(self.layLists)
1926
1927 - def set_multi_line(self, x):
1930 - def set_auto_scale(self, x):
1933 - def do_auto_scale(self):
1934 if self.auto_scale: 1935 self.__serial_auto_scale += 1 1936 gobject.idle_add(self.__do_auto_scale, self.__serial_auto_scale)
1937 - def __do_auto_scale(self, serial):
1938 if (serial == self.__serial_auto_scale) and self.signals: # the base view with no data file open has no .signals 1939 for i in xrange(1, self.signals.size): 1940 if self.signals[i, 'Center'] != SIGNAL_CENTER_HIDDEN: 1941 self.center_signal(i)
1942
1943 - def set_file(self, file):
1944 if self._file: 1945 if self._file.signals: # not blank 1946 self._file.signals.OnInsert -= self.__ref(self.__onInsertSignal) 1947 self._file.signals.OnRemoving -= self.__ref(self.__onRemovingSignal) 1948 self._file.signals.OnSet -= self.__ref(self.__onSetFileSignal) 1949 self._file.segments.OnSelect -= self.__ref(self.__onSelectSegment) 1950 self._file.OnSaving -= self.__ref(self.__onSaving) 1951 self._file.lists.OnSelect -= self.__ref(self.__onSelectInList) 1952 self._file.OnChangeSamples -= self.__ref(self.__onChangeSamples) 1953 if self.signals: 1954 while self.signals.size > 1: 1955 self.__onRemovingSignal(self.signals.size-2, False) 1956 1957 QubDataView_Base.set_file(self, file) 1958 1959 if self._file: 1960 if self._file.signals: # not blank 1961 self._file.signals.OnInsert += self.__ref(self.__onInsertSignal) 1962 self._file.signals.OnRemoving += self.__ref(self.__onRemovingSignal) 1963 self._file.signals.OnSet += self.__ref(self.__onSetFileSignal) 1964 self._file.segments.OnSelect += self.__ref(self.__onSelectSegment) 1965 self._file.OnSaving += self.__ref(self.__onSaving) 1966 self._file.lists.OnSelect += self.__ref(self.__onSelectInList) 1967 self._file.OnChangeSamples += self.__ref(self.__onChangeSamples) 1968 prefs = file.qsf.find('Scope') 1969 if prefs['Center'].data: 1970 self.signals[0, 'Center'] = prefs['Center'].data[0] 1971 self.signals[0, 'per div'] = prefs['per div'].data[0] 1972 for i in xrange(file.signals.size): 1973 self.__onInsertSignal(i, False) 1974 ####self.__onAfterInsertSignal(i, False) 1975 else: # sloppy...will permanently blank it: 1976 self.signals = None 1977 self.layerset = None 1978 self.time.file = file 1979 self.hires.file = file
1980
1981 - def get_center_sample(self):
1982 """Returns the index of the sample under the hilite or cross-hairs, hi-res if avail""" 1983 if self.hires.time.sel_left < self.hires.time.sel_right: 1984 f,l = self.hires.get_sels_screen(hilite=True)[0] 1985 else: 1986 sels = self.get_sels_screen() 1987 if sels: 1988 f,l = sels[0] 1989 else: 1990 f,l = 0, self.file.segmentation.last 1991 f,l = self.get_sels_screen()[0] 1992 return int(round(0.5*(f + l)))
1993
1994 - def __onSaving(self, file, tables):
1995 tables.append(self.signals)
1996 - def __onInsertSignal(self, i, undoing=False):
1997 rec = {'Name' : self._file.signals.get(i, 'Name'), 1998 'Units' : self._file.signals.get(i, 'Units')} 1999 qsf = self._file.qsf 2000 scope = qsf.find('Scope') 2001 if (i+1) < scope['Center'].data.count: 2002 rec['Center'] = scope['Center'].data[i+1] 2003 rec['per div'] = scope['per div'].data[i+1] 2004 r = 0 2005 for Name, Units in izip(qubx.tree.children(scope['Name'], 'Row'), 2006 qubx.tree.children(scope['Units'], 'Row')): 2007 if r == (i+1): 2008 rec['Name'] = str(Name.data) 2009 rec['Units'] = str(Units.data) 2010 break 2011 r += 1 2012 else: # classic QuB: 2013 scale = self._file.signals.get(i, 'Scale') / self._file.scaling 2014 off = self._file.signals.get(i, 'Offset') 2015 ilo = self._file.qsf.find('Display').find('DataYMin').data 2016 ilo = (i < len(ilo)) and ilo[i] or -10000 2017 ihi = self._file.qsf.find('Display').find('DataYMax').data 2018 ihi = (i < len(ihi)) and ihi[i] or 10000 2019 if (ilo != -10000) or (ihi != 10000): 2020 flo = ilo * scale + off 2021 fhi = ihi * scale + off 2022 rec['Center'] = (fhi + flo) / 2.0 2023 rec['per div'] = (fhi - flo) / 4.0 2024 self.signals.insert(i+1, rec) 2025 if not ('Center' in rec): 2026 gobject.idle_add(lambda: self.center_signal(i+1)) 2027 self.file_info.invalidate()
2028 - def __onRemovingSignal(self, i, undoing=False):
2029 self.signals.remove(i+1) 2030 self.file_info.invalidate()
2031 - def __onSetFileSignal(self, i, field_name, val, prev, undoing=False):
2032 if field_name in ['Name', 'Units']: 2033 self.signals.set(i+1, field_name, val)
2034 - def __onAfterInsertSignal(self, index, undoing=False):
2035 layer = self.sig_layers[index] 2036 try: 2037 layer.chkClearFitOnscreen 2038 except: 2039 layer.chkClearFitOnscreen = build_menuitem('Clear fit curves (onscreen)', self.__ref(bind(self.__onClickClearFit, False, index-1)), menu=layer.popup) 2040 layer.chkClearFitFile = build_menuitem('Clear fit curves (whole file)', self.__ref(bind(self.__onClickClearFit, True, index-1)), menu=layer.popup) 2041 layer.chkIdlAbove = build_menuitem('Idealized above', self.__ref(bind(self.__onToggleIdlAbove, index)), menu=layer.popup, item_class=gtk.CheckMenuItem) 2042 layer.chkIdlAbove.set_active(self.signals[index, 'Idl above'])
2043 - def __onClickClearFit(self, whole_file, index):
2044 if not self.file: return 2045 if whole_file: 2046 qubx.pyenv.env.OnScriptable('QubX.Data.file.fits[%i].idl.clear()' % (index)) 2047 self.file.fits[index].idl.clear() 2048 else: 2049 seg = self.get_segmentation_hires()[0] 2050 qubx.pyenv.env.OnScriptable('QubX.Data.file.fits[%i].idl.erase(%i, %i)' % (index, seg.f, seg.l)) 2051 self.file.fits[index].idl.erase(seg.f, seg.l) 2052 self.file.OnChangeFits(self.file, index)
2053 - def __onToggleIdlAbove(self, index):
2054 if self.__ignore_evt: return 2055 x = self.sig_layers[index].chkIdlAbove.get_active() 2056 qubx.pyenv.env.OnScriptable('QubX.Data.view.signals[%i, "Idl above"] = %s' % (index, repr(x))) 2057 self.signals[index, 'Idl above'] = x
2058 - def __onSetSignal(self, i, field_name, val, prev, undoing=False):
2059 if field_name == 'Idl above': 2060 self.__ignore_evt = True 2061 self.sig_layers[i].chkIdlAbove.set_active(val) 2062 self.__ignore_evt = False
2063 - def __onSelectSegment(self, i, field_name, sender):
2064 if sender != self: # sender == self.QubX.Tables.find_view('Segments'): 2065 self.time.Iseg = i
2066 - def __onChangeShowing(self, time):
2067 self.raw_bounds = (time.left, time.right) 2068 self.do_auto_scale()
2069 - def __onChangeSel(self, time, left, right):
2070 self.data_overlay.invalidate() 2071 self.redraw_canvas(False) 2072 self.update_hint(0, 0)
2073 - def __onChangeSamples(self, file, i=-1):
2074 self.do_auto_scale()
2075 - def __onSelectInList(self, index, field_name, sender):
2076 if index is None: return 2077 self.show_list_item(index)
2078 - def show_list_item(self, index):
2079 self.list_sel = index 2080 self.redraw_canvas(True) 2081 first, last = self.file.list[index, 'From'], self.file.list[index, 'To'] 2082 # move to segment 2083 if not (index is None): 2084 segf = self.file.segmentation.index_at(first) 2085 if not (self.time.Iseg <= segf < (self.time.Iseg + self.time.Gseg)): 2086 self.time.Iseg = segf 2087 # hightlight sel 2088 segf = self.file.segmentation.segments[self.file.segmentation.index_at(first)][0] 2089 sampling = self.file.sampling 2090 self.time.set_sel(sampling *(first - segf), sampling * (last - segf))
2091 - def __onZoom(self, sublayer, factor):
2092 per_div = self.signals[0, 'per div'] 2093 self.signals[0, 'per div'] = per_div / factor
2094 - def zoom_in(self, left, right):
2095 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.zoom_in(left=%s, right=%s)' % (left, right)) 2096 self.time.zoom_in(left=left, right=right)
2097 - def zoom_in_at(self, x, y, e):
2098 center, t_per_div = [self.signals.get(0, field) for field in ('Center', 'per div')] 2099 xsec = self.x2u(x, y, center, t_per_div) 2100 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.zoom_in(); QubX.Data.view.signals[0, "Center"] = %s' % xsec) 2101 self.time.zoom_in() 2102 self.signals.set(0, 'Center', xsec)
2103 - def onClickUnGrab(self, signal):
2104 if not self.file: return 2105 qubx.pyenv.env.OnScriptable('QubX.Data.view.hide_signal(%i)' % signal.index) 2106 self.hide_signal(signal.index)
2107 - def onClickGrab(self, signal):
2108 if not self.file: return 2109 if signal.index: 2110 if not self.auto_scale: 2111 qubx.pyenv.env.OnScriptable('QubX.Data.view.center_signal(%i)' % signal.index) 2112 self.center_signal(signal.index) 2113 else: 2114 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.timeRange.set_range(*QubX.Data.view.time.timeRange.bounds)') 2115 self.time.timeRange.set_range(*self.time.timeRange.bounds)
2116 - def center_signal(self, index):
2117 i = index - 1 2118 if i == -1: 2119 self.time.pop_range(True) 2120 return 2121 QubDataView_Base.center_signal(self, index)
2122
2123 - def get_segmentation_hires(self, signal=None, latency=None, baseline_nodes=True):
2124 return self.hires.get_segmentation_screen(signal=signal, latency=latency, baseline_nodes=baseline_nodes)
2125 - def measure(self, left, right, source, to_list=True, script_name=None, wait=True):
2126 return self.hires.measure(left, right, source, to_list, script_name, wait)
2127 - def measure_list(self, list, script_name=None, wait=True, receiver=lambda tbl, off, ct: None):
2128 return self.hires.measure_list(list, script_name, wait, receiver)
2129 - def measure_segments(self, segs, table=None, row_offset=None, script_name=None, receiver=lambda tbl, off, ct: None):
2130 return self.hires.measure_segments(segs, table, row_offset, script_name, receiver)
2131 - def measure_datasource(self, script_name=None, receiver=lambda tbl, off, ct: None):
2132 return self.hires.measure_datasource(script_name=script_name, receiver=receiver)
2133
2134 - def draw_custom_overlay(self, context, w, h):
2135 sel_left, sel_right = self.time.sel_left, self.time.sel_right 2136 if sel_left < sel_right: 2137 t_per_div = self.signals.get(0, 'per div') 2138 pix_per_sec = self.pix_per_div[0] / t_per_div 2139 context.set_source_rgba(* self.appearance.color(COLOR_SEL)) 2140 2141 draw_bounds = self.draw_bounds.val 2142 line_dur = (draw_bounds[1] - draw_bounds[0]) / self.line_count 2143 line_height = h / self.line_count 2144 line_left = draw_bounds[0] 2145 for l in xrange(self.line_count): 2146 left, right = line_isect(sel_left, sel_right, line_left, line_left+line_dur) 2147 if left < right: 2148 context.rectangle(pix_per_sec * (left - line_left), l*line_height, pix_per_sec * (right - left), line_height) 2149 context.fill() 2150 line_left += line_dur
2151
2152 - def __onSel(self, left, right):
2153 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.set_sel(%s, %s)' % (left, right)) 2154 self.time.set_sel(left, right)
2155 - def __onDblClick(self, x, y, event):
2156 if self.time.sel_left <= self.x2u(x, y, self.signals[0, 'Center'], self.signals[0, 'per div']) <= self.time.sel_right: 2157 self.time.timeRange.set_range(self.time.sel_left, self.time.sel_right) 2158 else: 2159 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.set_sel(*QubX.Data.view.time.timeRange.bounds)') 2160 self.time.timeRange.set_range(*self.time.timeRange.bounds) 2161 self.time.set_sel(*self.time.timeRange.bounds)
2162 - def __onScroll(self, x, y, event, offset):
2163 if event.state & gdk.SHIFT_MASK: 2164 offset *= 5 2165 factor = pow(1.025, abs(offset)) 2166 if offset < 0: 2167 factor = 1.0 / factor 2168 time_end = self.time.timeRange.bounds[1] 2169 p0 = self.time.left / time_end 2170 p1 = self.time.right / time_end 2171 p_in_frame = x * 1.0 / self._dim[0] 2172 p = p0 + p_in_frame * (p1 - p0) 2173 w = min(1.0, (p1 - p0)/factor) 2174 p0 = max(0.0, p - p_in_frame*w) 2175 p1 = min(1.0, p + (1.0 - p_in_frame)*w) 2176 self.time.timeRange.set_range(p0 * time_end, p1 * time_end)
2177
2178 -def line_isect(l0, r0, l1, r1):
2179 return (max(l0, l1), min(r0, r1))
2180
2181 2182 -class QubDataView_Hi(QubDataView_Base):
2183 """High-resolution data view 2184 2185 """ 2186 2187 __explore_featured = ['robot', 'request_fit_controls', 'layZoom', 'layTools', 'tool_ruler', 'tool_pan', 2188 'tool_exclude', 'subFit', 'tool_baseline', 'subIdealize', 'subOtherTools', 2189 'tool_baum_welch', 'mnu_baum_welch', 'subAMP', 2190 'run_baum_welch', 'run_baum_welch_list', 'run_baum_welch_segments', 2191 'get_sels_screen', 'measure', 'measure_list', 'measure_segments', 'measure_datasource', 'update_hint'] 2192
2193 - def __init__(self, signals_lo, time):
2194 QubDataView_Base.__init__(self, 'QubX.Data.view.hires', False) 2195 self.hires = self 2196 self.__ref = Reffer() 2197 self.signals.OnSet += self.__ref(self.__onSetSignals) 2198 self.signals_lo = signals_lo 2199 self.signals_lo.OnInsert += self.__ref(self.__onInsertSignals_lo) 2200 self.signals_lo.OnRemoving += self.__ref(self.__onRemovingSignals_lo) 2201 self.signals_lo.OnSet += self.__ref(self.__onSetSignals_lo) 2202 self.time = time 2203 self.time.OnChangeSel += self.__ref(self.__onChangeSel) 2204 self.time.OnChangeHilite += self.__ref(self.__onChangeHilite) 2205 2206 self.notebook['Pick'] = qubx.notebookGTK.NbSubTablePicker(self, "%s.notebook['Pick']"%self.global_name, self.nb_get_caption, NbSubTable='qubx.dataGTK.NbSubTrace_Hi') 2207 self.subNotebook.items.append(self.notebook['Pick']) 2208 2209 self.request_fit_controls = lambda showing: None # replaced by DatasFace 2210 self.__meas_restore_layerset = None 2211 2212 self.layZoom = qubx.toolspace.Layer(x=-5, y=-5, w=4.5, h=4.5, cBG=COLOR_ZOOM_BG) 2213 self.controls.add_layer(self.layZoom) 2214 self.subZoomOut = qubx.toolspace.SubLayer_SmoothZoom('-', zoom_out=True, x=1.5, y=3, w=1.5, h=1.5) 2215 self.subZoomOut.OnZoom += self.__ref(self.__onZoom) 2216 self.layZoom.add_sublayer(self.subZoomOut) 2217 self.subZoomIn = qubx.toolspace.SubLayer_SmoothZoom('+', x=1.5, y=0, w=1.5, h=1.5) 2218 self.subZoomIn.OnZoom += self.__ref(self.__onZoom) 2219 self.layZoom.add_sublayer(self.subZoomIn) 2220 2221 self.subZoomOutTime = qubx.toolspace.SubLayer_SmoothZoom('-', zoom_out=True, x=0, y=1.5, w=1.5, h=1.5) 2222 self.subZoomOutTime.OnZoom += self.__ref(self.__onZoomTime) 2223 self.layZoom.add_sublayer(self.subZoomOutTime) 2224 self.subZoomInTime = qubx.toolspace.SubLayer_SmoothZoom('+', x=3, y=1.5, w=1.5, h=1.5) 2225 self.subZoomInTime.OnZoom += self.__ref(self.__onZoomTime) 2226 self.layZoom.add_sublayer(self.subZoomInTime) 2227 2228 self.subMoveLeft = qubx.toolspace.SubLayer_Label('<', 0, 1, x=0, y=3, w=1.5, h=1.5, border=1, cBorder=qubx.toolspace.COLOR_ZOOM, 2229 color=qubx.toolspace.COLOR_ZOOM, hover_color=qubx.toolspace.COLOR_ZOOM_HOVER, 2230 action=self.__ref(self.__onClickMoveLeft)) 2231 self.layZoom.add_sublayer(self.subMoveLeft) 2232 self.subMoveRight = qubx.toolspace.SubLayer_Label('>', 0, 1, x=3, y=3, w=1.5, h=1.5, border=1, cBorder=qubx.toolspace.COLOR_ZOOM, 2233 color=qubx.toolspace.COLOR_ZOOM, hover_color=qubx.toolspace.COLOR_ZOOM_HOVER, 2234 action=self.__ref(self.__onClickMoveRight)) 2235 self.layZoom.add_sublayer(self.subMoveRight) 2236 2237 self.layTools = qubx.toolspace.Layer_Toolbar(dim=0, x=4, y=-4, w=1, h=3, cBG=qubx.scope.COLOR_SIGNAL_BG) 2238 self.controls.add_layer(self.layTools) 2239 self.tool_ruler = QubData_MeasureTool() 2240 self.subMeasure = qubx.toolspace.SubLayer_Ruler(action=self.__ref(self.__onClickMeasure)) 2241 self.layTools.add_tool('Pick measurement cursor', self.subMeasure, self.tool_ruler) 2242 self.robot = qubx.task.Robot('Measure', self.__ref(lambda: qubx.task.Tasks.add_task(self.robot)), 2243 self.__ref(lambda: qubx.task.Tasks.remove_task(self.robot))) 2244 self.robot.OnException += self.__ref(self.__onRobotException) 2245 self.robot.in_use = False 2246 self.subPan = qubx.toolspace.SubLayer_PanIcon() 2247 self.tool_pan = QubData_PanTool() 2248 self.layTools.add_tool('Pick scrolling cursor', self.subPan, self.tool_pan) 2249 self.layTools.add_tool('Go to specific selection', 2250 qubx.toolspace.SubLayer_Label('Go', 0, 1, w=2.5, h=2.5, action=self.__ref(self.__onClickGo), 2251 color=COLOR_DATA_LAYER_FG, hover_color=COLOR_DATA_LAYER_HOVER)) 2252 self.tool_exclude = QubData_ExcludeTool() 2253 self.subExclude = qubx.toolspace.SubLayer_Eraser(action=self.__ref(self.__onClickExclude)) 2254 self.layTools.add_tool('Exclude data...', self.subExclude, self.tool_exclude) 2255 self.tool_clone = QubData_CloneTool() 2256 self.subClone = qubx.toolspace.SubLayer_Stamp(action=self.__ref(self.__onClickClone)) 2257 self.layTools.add_tool('Clone/erase data...', self.subClone, self.tool_clone) 2258 self.subFit = qubx.fit_space.SubLayer_FitIcon(action=self.__ref(self.__onClickFit)) 2259 self.layTools.add_tool('Curve fitting...', self.subFit) 2260 self.tool_baseline = QubData_BaselineTool() 2261 self.subBaseline = qubx.toolspace.SubLayer_PNG(os.path.join(qubx.global_namespace.app_path, 'icons', 'baseline.png'), 2262 w=2.5, h=2.5, action=self.__ref(self.__onClickBaseline)) 2263 self.layTools.add_tool('Edit baseline and nodes', self.subBaseline, self.tool_baseline) 2264 if self.QubX.has_modeling: 2265 self.subIdealize = qubx.toolspace.SubLayer_PNG(os.path.join(qubx.global_namespace.app_path, 'icons', 'idealize.png'), 2266 w=2.5, h=2.5, action=self.__ref(self.__onClickIdealizationMenu)) 2267 self.layTools.add_tool('Idealization', self.subIdealize) 2268 else: 2269 self.subIdealize = None 2270 self.mnuOtherTools = gtk.Menu() 2271 self.subOtherTools = qubx.toolspace.SubLayer_MenuLines(self.mnuOtherTools, 'Other...', self.__ref(self.onPopupOtherTools), 2272 w=self.subBaseline.rq_w, h=self.subBaseline.rq_h) 2273 if self.QubX.has_modeling: 2274 self.tool_baum_welch = QubData_BaumWelchTool() 2275 self.mnu_baum_welch = gtk.Menu() 2276 self.itemBWUpdateModel = build_menuitem('Update Model (Classes:Amp and Std)', self.__ref(self.__onClickBWUpdateModel), 2277 item_class=gtk.CheckMenuItem, menu=self.mnu_baum_welch) 2278 self.itemBWShowIdeal = build_menuitem('Show idealization', self.__ref(self.__onClickBWShowIdeal), 2279 item_class=gtk.CheckMenuItem, menu=self.mnu_baum_welch) 2280 self.itemBWAutoInit = build_menuitem('Auto-init from data', self.__ref(self.__onClickBWAutoInit), 2281 item_class=gtk.CheckMenuItem, menu=self.mnu_baum_welch) 2282 self.itemBWAutoInitUp = build_menuitem('Opening in + direction (up)', self.__ref(self.__onClickBWAutoInitUp), 2283 item_class=gtk.CheckMenuItem, menu=self.mnu_baum_welch) 2284 self.subAMP = qubx.toolspace.SubLayer_Label('AMP', 0, 1, w=2.5, h=2.5, color=COLOR_DATA_LAYER_FG, hover_color=COLOR_DATA_LAYER_HOVER, 2285 action=self.__ref(self.__onClickAMP)) 2286 self.layTools.add_tool('Pick Baum-Welch cursor', self.subAMP, self.tool_baum_welch) 2287 else: 2288 self.tool_baum_welch = self.mnu_baum_welch = self.subAMP = None 2289 self.__bw_hint = "" 2290 self.__bw_hint_serial = 0 2291 self.__bw_chk_disable = False 2292 2293 self.layTools.add_tool('Other...', self.subOtherTools, QubData_BaseTool()) 2294 2295 self.QubX.OnQuit += self.__ref(lambda: self.robot.stop()) 2296 self.__screen_measurements = None 2297 self.__in_change_sel = False 2298 2299 self.__last_x = self.__last_y = 0 2300 2301 2302 for i in xrange(signals_lo.size): 2303 if i >= self.signals.size: 2304 self.__onInsertSignals_lo(i, False)
2305
2306 - def dispose(self):
2307 QubDataView_Base.dispose(self) 2308 self.robot.stop() 2309 self.__ref.clear()
2310
2311 - def set_file(self, file):
2312 if self._file: 2313 if self._file.signals: 2314 self._file.lists.OnSelect -= self.__ref(self.__onSelectInList) 2315 self._file.OnChangeBaselineNodes -= self.__ref(self.__onChangeBaselineNodes) 2316 QubDataView_Base.set_file(self, file) 2317 if self._file: 2318 if self._file.signals: 2319 self._file.lists.OnSelect += self.__ref(self.__onSelectInList) 2320 self._file.OnChangeBaselineNodes += self.__ref(self.__onChangeBaselineNodes)
2321
2322 - def __onSetSignals(self, i, field, val, prev, undoing):
2323 if i == 0: # time signal 2324 if self.__in_change_sel: return 2325 if val == prev: return 2326 if field == 'Center': 2327 center = val 2328 per_div = self.signals[i, 'per div'] 2329 elif field == 'per div': 2330 center = self.signals[i, 'Center'] 2331 per_div = val 2332 else: 2333 return 2334 time_end = self.time.timeRange.bounds[1] 2335 halfw = 0.5 * min(time_end, self.divs[0] * per_div) 2336 center = max(halfw, min(time_end-halfw, center)) 2337 self.time.set_sel(center-halfw, center+halfw) 2338 else: 2339 if hasattr(val, '__int__') and (numpy.isinf(val) or numpy.isnan(val)): return 2340 if self.signals_lo[i, field] == val: return 2341 self.signals_lo[i, field] = val
2342 - def __onSetSignals_lo(self, i, field, val, prev, undoing):
2343 if i == 0: return # time signal 2344 if hasattr(val, '__int__') and (numpy.isinf(val) or numpy.isnan(val)): return 2345 if self.signals[i, field] == val: return 2346 self.signals[i, field] = val
2347 - def __onInsertSignals_lo(self, i, undoing=False):
2348 self.signals.insert(i, self.signals_lo[i].fields)
2349 - def __onRemovingSignals_lo(self, i, undoing=False):
2350 self.signals.remove(i)
2351 - def __onChangeSel(self, time, left, right):
2352 self.raw_bounds = (time.sel_left, time.sel_right) 2353 self.__in_change_sel = True 2354 self.signals[0, 'per div'] = ((time.sel_right - time.sel_left) / self.divs[0]) or 1.0 2355 self.signals[0, 'Center'] = (time.sel_left + time.sel_right) / 2.0 2356 self.__in_change_sel = False 2357 if not self._drawing: 2358 self.redraw_canvas()
2359 - def __onChangeHilite(self, time, left, right):
2360 if not (left is None): 2361 self.measure(left, right, DATASOURCE_SCREEN, False, wait=False) 2362 else: 2363 self.__screen_measurements = None 2364 if not self._drawing: 2365 self.redraw_canvas()
2366 - def __onSelectInList(self, index, field_name, sender):
2367 self.list_sel = index 2368 self.redraw_canvas(True)
2369 - def __onChangeBaselineNodes(self, file, signal):
2370 self.tool_baseline.onChangeBaselineNodes(signal)
2371
2372 - def __onZoom(self, sublayer, factor):
2373 per_div = self.signals[self.QubX.DataSource.signal+1, 'per div'] 2374 self.signals[self.QubX.DataSource.signal+1, 'per div'] = per_div / pow(factor, 3)
2375 - def __onZoomTime(self, sublayer, factor):
2376 per_div = self.signals[0, 'per div'] 2377 self.signals[0, 'per div'] = per_div / factor
2378 - def __onClickMoveLeft(self, x, y, e):
2379 w = self.divs[0]*self.signals[0, 'per div'] 2380 self.signals[0, 'Center'] = max(w/2.0, self.signals[0, 'Center'] - w)
2381 - def __onClickMoveRight(self, x, y, e):
2382 w = self.divs[0] * self.signals[0, 'per div'] 2383 self.signals[0, 'Center'] = min(self.time.timeRange.bounds[1] - w/2.0, self.signals[0, 'Center'] + w)
2384
2385 - def __onClickGo(self, x, y, e):
2386 result = qubx.GTK.PromptEntries([('Segment:', self.time.Iseg+1, acceptIntGreaterThan(0), str), 2387 ('Selection start (ms in segment):', self.time.sel_left*1e3, acceptFloatBetween(0.0, 1e3*self.time.timeRange.bounds[1]), "%.3g"), 2388 ('Selection end (ms in segment):', self.time.sel_right*1e3, acceptFloatBetween(0.0, 1e3*self.time.timeRange.bounds[1]), "%.3g")], 2389 "%s - Go to selection" % qubx.global_namespace.QubX.appname, 2390 qubx.global_namespace.QubX.Data.parent_window) 2391 if result: 2392 self.time.Iseg = result[0] - 1 2393 left, right = result[1]*1e-3, result[2]*1e-3 2394 if left > right: 2395 left, right = right, left 2396 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.set_sel(%s, %s)' % (left, right)) 2397 self.__in_change_sel = True 2398 self.time.set_sel(left, right) 2399 self.__in_change_sel = False
2400 - def __onClickExclude(self, x, y, e):
2401 self.subExclude.OnHideTooltip(self.subExclude) 2402 self.tool_exclude.do_popup(x, y, e)
2403 - def __onClickClone(self, x, y, e):
2404 self.subClone.OnHideTooltip(self.subClone) 2405 self.tool_clone.do_popup(x, y, e)
2406 - def __onClickMeasure(self, x, y, e):
2407 if not self.file: return 2408 self.subMeasure.OnHideTooltip(self.subMeasure) 2409 self.tool_ruler.do_popup(x, y, e)
2410 - def __onClickBaseline(self, x, y, e):
2411 self.subBaseline.OnHideTooltip(self.subBaseline) 2412 self.tool_baseline.do_popup(x, y, e)
2413 - def __onClickFit(self, x, y, e):
2414 self.tool = self.tool_pan 2415 qubx.pyenv.env.OnScriptable('QubX.Data.view.request_fit_controls(True)') 2416 gobject.idle_add(self.request_fit_controls, True)
2417 - def __onClickIdealizationMenu(self, x, y, e):
2418 ### TODO: OnScriptable for menu items 2419 popup = gtk.Menu() 2420 self.__ref_popupIdl = Reffer() 2421 try: 2422 build_menuitem('Run Idealizer (on Data Source)', self.__ref_popupIdl(bind_scriptable('QubX.Modeling.Idealize.idealize()', 2423 qubx.global_namespace.QubX.Modeling.Idealize.idealize, wait=False)), menu=popup) 2424 if self._file.undoIdl.can_undo: 2425 build_menuitem('Undo', self.__ref_popupIdl(bind(self.file.undoIdl.undo)), menu=popup) 2426 if self._file.undoIdl.can_redo: 2427 build_menuitem('Redo', self.__ref_popupIdl(bind(self.file.undoIdl.redo)), menu=popup) 2428 build_menuitem('Show Idealize panel...', self.__ref_popupIdl(bind(qubx.global_namespace.QubX.Modeling.Idealize.request_show)), menu=popup) 2429 except: # no QubX.Modeling.Idealize? 2430 traceback.print_exc() 2431 idlTools = self.QubX.Tools.Idealization 2432 build_menuitem('Measure event statistics', self.__ref_popupIdl(bind(self.__onClickEventStats)), menu=popup) 2433 build_menuitem('Measure stability (stats over time)...', self.__ref_popupIdl(bind(idlTools.onClickStability, None)), menu=popup) 2434 build_menuitem('Save as DWT...', self.__ref_popupIdl(bind(idlTools.onClickSaveDWT, None, False)), menu=popup) 2435 build_menuitem('Save as DWT with event stats...', self.__ref_popupIdl(bind(idlTools.onClickSaveDWT, None, True)), menu=popup) 2436 build_menuitem('Save as sampled text (using class index)...', self.__ref_popupIdl(bind(idlTools.onClickSaveSampled, None, False)), menu=popup) 2437 build_menuitem('Save as sampled text (using class amp)...', self.__ref_popupIdl(bind(idlTools.onClickSaveSampled, None, True)), menu=popup) 2438 build_menuitem('Clear idealization (onscreen)', self.__ref_popupIdl(bind(idlTools.onClickClear, None, False)), menu=popup) 2439 build_menuitem('Clear idealization (whole file)', self.__ref_popupIdl(bind(idlTools.onClickClear, None, True)), menu=popup) 2440 build_menuitem('Make list of events...', self.__ref_popupIdl(bind(idlTools.onClickListEvents, None)), menu=popup) 2441 build_menuitem('Make list of transitions...', self.__ref_popupIdl(bind(idlTools.onClickListTransitions, None)), menu=popup) 2442 build_menuitem('Make list of bursts (Chop)...', self.__ref_popupIdl(bind(idlTools.onClickChop, None)), menu=popup) 2443 idl_tools = Tools.by_cat['ideal'] 2444 for tool_label in sorted(idl_tools.keys()): 2445 action, tool_class = idl_tools[tool_label] 2446 build_menuitem(tool_label, action or self.__ref_popupIdl(bind(lambda: self.set_tool(tool_class()))), menu=popup) 2447 build_menuitem('More utilities...', self.__ref_popupIdl(bind(qubx.global_namespace.QubX.Tools.Idealization.request_show)), menu=popup) 2448 popup.popup(None, None, None, 0, e.time)
2449 - def __onClickAMP(self, x, y, e):
2450 if not self.file: return 2451 idlTools = self.QubX.Tools.Idealization 2452 self.__bw_chk_disable = True 2453 self.itemBWUpdateModel.set_active(idlTools.bw_update_model) 2454 self.itemBWShowIdeal.set_active(idlTools.bw_show_ideal) 2455 self.itemBWAutoInit.set_active(idlTools.bw_auto_init) 2456 self.itemBWAutoInitUp.set_active(idlTools.bw_auto_init_up) 2457 self.__bw_chk_disable = False 2458 self.mnu_baum_welch.popup(None, None, None, 0, e.time)
2459 - def onPopupOtherTools(self):
2460 tools = Tools.by_cat['other'] 2461 if not tools: return 2462 popup = self.mnuOtherTools 2463 popup.ref = Reffer() 2464 popup.foreach(lambda item: popup.remove(item)) 2465 for tool_label in sorted(tools.keys()): 2466 action, tool_class = tools[tool_label] 2467 build_menuitem(tool_label, action or popup.ref(bind(lambda: self.set_tool(tool_class()))), menu=popup) 2468 return True
2469 - def __onClickEventStats(self):
2470 qubx.pyenv.env.OnScriptable('QubX.Data.view.measure_datasource(script_name="Idealized.py")') 2471 self.measure_datasource(script_name='Idealized.py', wait=False)
2472 - def __onClickBWAutoInit(self, item):
2473 if not self.__bw_chk_disable: 2474 self.QubX.Tools.Idealization.propertied_on_user_set('bw_auto_init', not self.QubX.Tools.Idealization.bw_auto_init)
2475 - def __onClickBWAutoInitUp(self, item):
2476 if not self.__bw_chk_disable: 2477 self.QubX.Tools.Idealization.propertied_on_user_set('bw_auto_init_up', not self.QubX.Tools.Idealization.bw_auto_init_up)
2478 - def __onClickBWUpdateModel(self, item):
2479 if not self.__bw_chk_disable: 2480 self.QubX.Tools.Idealization.propertied_on_user_set('bw_update_model', not self.QubX.Tools.Idealization.bw_update_model)
2481 - def __onClickBWShowIdeal(self, item):
2482 if not self.__bw_chk_disable: 2483 self.QubX.Tools.Idealization.propertied_on_user_set('bw_show_ideal', not self.QubX.Tools.Idealization.bw_show_ideal)
2484 - def run_baum_welch(self, left, right, show_ideal=None, wait=True, receiver=lambda result: None):
2485 idlTools = self.QubX.Tools.Idealization 2486 model = self.QubX.Models.file 2487 if model.states: 2488 config = qubopt.amp.get_amp_config(model, idlTools.bw_auto_init, idlTools.bw_auto_init_up) 2489 segments = self.get_segmentation(left=left, right=right, source=DATASOURCE_SCREEN) 2490 update_model = model if idlTools.bw_update_model else None 2491 show_ideal_ = idlTools.bw_show_ideal if (show_ideal is None) else show_ideal 2492 if wait: 2493 return qubx.pyenv.call_async_wait(lambda rcv: self.run_baum_welch_segments(config, segments, update_model, show_ideal_, receiver=rcv, on_result=self.baum_welch_show_segment_result))[0] 2494 else: 2495 self.run_baum_welch_segments(config, segments, update_model, show_ideal_, receiver, self.baum_welch_show_segment_result) 2496 else: 2497 result = qubx.tree_native.NullNode() 2498 if wait: 2499 return result 2500 else: 2501 gobject.idle_add(receiver, result)
2502 - def run_baum_welch_list(self, show_ideal=None, wait=True, receiver=lambda result: None):
2503 idlTools = self.QubX.Tools.Idealization 2504 model = self.QubX.Models.file 2505 lst = self.__bw_list = self.file.list 2506 if model.states and lst: 2507 config = qubopt.amp.get_amp_config(model, idlTools.bw_auto_init, idlTools.bw_auto_init_up) 2508 segments = self.get_segmentation_list() 2509 update_model = model if idlTools.bw_update_model else None 2510 show_ideal_ = idlTools.bw_show_ideal if (show_ideal is None) else show_ideal 2511 if wait: 2512 return qubx.pyenv.call_async_wait(lambda rcv: self.run_baum_welch_segments(config, segments, update_model, show_ideal_, receiver=rcv, on_result=self.baum_welch_show_list_result))[0] 2513 else: 2514 self.run_baum_welch_segments(config, segments, update_model, show_ideal_, receiver, on_result=self.baum_welch_show_list_result) 2515 else: 2516 result = qubx.tree_native.NullNode() 2517 if wait: 2518 return result 2519 else: 2520 gobject.idle_add(receiver, result)
2521 - def run_baum_welch_segments(self, config, segments, update_model=None, show_ideal=False, receiver=lambda result: None, on_result=lambda result: None):
2522 self.robot.do(self.robot_run_baum_welch_segments, config, segments, update_model, show_ideal, receiver, on_result)
2523 - def robot_run_baum_welch_segments(self, config, segments, update_model, show_ideal, receiver, on_result):
2524 def on_report(s): 2525 print s
2526 def on_percent(p): 2527 self.robot.progress = p
2528 try: 2529 result = qubopt.amp.AMP(config, segments, on_report, on_percent, self.robot.main_hold) 2530 gobject.idle_add(on_result, segments, result, update_model, show_ideal) 2531 2532 except: 2533 traceback.print_exc() 2534 result = qubx.tree_native.NullNode() 2535 receiver(result)
2536 - def baum_welch_show_misc_result(self, segments, result, update_model, show_ideal):
2537 if update_model: 2538 amp = result['amp'].data 2539 sd = result['sd'].data 2540 cc = update_model.classes 2541 for i in xrange(min(len(amp), len(sd))): 2542 cc[i, 'Amp'] = amp[i] 2543 cc[i, 'Std'] = sd[i] 2544 2545 # idealized (max gamma of baum-welch) 2546 if show_ideal: 2547 if segments: 2548 updater = self._file.update_idl(segments[0].signal) 2549 for seg, res in izip(segments, qubx.tree.children(result['DataSet'], 'Segment')): 2550 ndwell = res.find('DwellCount').data 2551 ndwell = ndwell[0] if len(ndwell) else 0 2552 if ndwell: 2553 updater.set_dwells(ndwell, res['Firsts'].storage.data, res['Lasts'].storage.data, res['Classes'].storage.data, 2554 res['amp'].data[:], res['sd'].data[:]) 2555 if segments: 2556 updater.done()
2557
2558 - def baum_welch_show_segment_result(self, segments, result, update_model, show_ideal):
2559 self.baum_welch_show_misc_result(segments, result, update_model, show_ideal) 2560 2561 buf = cStringIO.StringIO() 2562 buf.write("Baum-Welch (AMP) results [Class: mean +/- std]:\n\n") 2563 for seg, res in izip(segments, qubx.tree.children(result["DataSet"], "Segment")): 2564 if len(segments) > 1: 2565 buf.write("Segment %i:\n" % (seg.index+1)) 2566 amp = res["amp"].data 2567 sd = res["sd"].data 2568 for i in xrange(min(len(amp), len(sd))): 2569 buf.write(" %i: %.4g +/- %.4g\n" % (i, amp[i], sd[i])) 2570 buf.write("\n") 2571 2572 amp = result["amp"].data 2573 sd = result["sd"].data 2574 if len(amp) > 1: 2575 exc_amp = [a - amp[0] for a in amp] 2576 exc_sd = [sqrt(max(0.0, s**2 - sd[0]**2)) for s in sd] 2577 buf.write("Delta amp and excess std:\n\n") 2578 for i in xrange(1, len(exc_amp)): 2579 buf.write(" %i: %.4g +/- %.4g\n" % (i, exc_amp[i], exc_sd[i])) 2580 buf.write("\n") 2581 self.__bw_hint_serial += 1 2582 self.__bw_hint = buf.getvalue() 2583 gobject.timeout_add(BW_HINT_EXPIRE_SEC*1000, self.__expire_bw_hint, self.__bw_hint_serial) 2584 self.update_hint()
2585 - def baum_welch_show_list_result(self, segments, result, update_model, show_ideal):
2586 self.baum_welch_show_misc_result(segments, result, update_model, show_ideal) 2587 2588 lst = self.__bw_list 2589 for s, res in enumerate(qubx.tree.children(result['DataSet'], 'Segment')): 2590 amp = res['amp'].data 2591 sd = res['sd'].data 2592 for i in xrange(len(amp)): 2593 lst[s, 'BW Amp %i' % i] = amp[i] 2594 lst[s, 'BW Std %i' % i] = sd[i] 2595 if i: 2596 lst[s, 'BW Delta Amp %i' % i] = (amp[i] - amp[0]) 2597 lst[s, 'BW Exc Std %i' % i] = sqrt(max(0.0, sd[i]**2 - sd[0]**2))
2598 - def __expire_bw_hint(self, serial):
2599 if serial == self.__bw_hint_serial: 2600 self.__bw_hint = ""
2601
2602 - def get_sels_screen(self, left=None, right=None, hilite=False):
2603 if hilite and (left is None) and (right is None) and not (self.time.hilite[0] is None): 2604 segs = self.get_segmentation(left=self.time.hilite[0], right=self.time.hilite[1], source=DATASOURCE_SCREEN) 2605 return [(seg.f, seg.l) for seg in segs] 2606 else: 2607 return QubDataView_Base.get_sels_screen(self, left=left, right=right)
2608
2609 - def draw_custom_overlay(self, context, w, h):
2610 left, right = self.time.hilite 2611 if (not (left is None)) and (left < right): 2612 t_per_div = self.signals.get(0, 'per div') 2613 pix_per_sec = self.pix_per_div[0] / t_per_div 2614 context.set_source_rgba(* self.appearance.color(COLOR_SEL)) 2615 draw_bounds = self.draw_bounds.val 2616 context.rectangle(pix_per_sec * (left - draw_bounds[0]), 0, pix_per_sec * (right - left), h) 2617 context.fill()
2618
2619 - def __onRobotException(self, robot, typ, val, trace):
2620 traceback.print_exception(typ, val, trace)
2621 - def measure(self, left, right, source, to_list=True, script_name=None, wait=True):
2622 2623 """Initiates amp,std measurement of all signals, within time range.""" 2624 if to_list: 2625 measured = self.file.lists.show_list('Measured') 2626 if source == DATASOURCE_FILE: 2627 measured.clear() 2628 self.__screen_measurements = None 2629 else: 2630 measured = qubx.table.SimpleTable('', auto_add_fields=True) 2631 self.__screen_measurements = measured 2632 segments = self.get_segmentation(left=left, right=right, source=source, signal=qubx.pyenv.env.globals['QubX'].DataSource.signal) 2633 if wait: 2634 return qubx.pyenv.call_async_wait(lambda rcv: self.measure_segments(segments, table=measured, row_offset=measured.size, script_name=script_name, receiver=rcv)) 2635 else: 2636 self.measure_segments(segments, table=measured, row_offset=measured.size, script_name=script_name)
2637 - def measure_list(self, list, script_name=None, wait=True, receiver=lambda tbl, off, ct: None):
2638 """Returns (Table, row_offset, row_count), either directly if wait is True,x or via receiver function.""" 2639 segments = self.file.get_segmentation_list(list.list_name, signal=qubx.pyenv.env.globals['QubX'].DataSource.signal) 2640 if not segments: return 2641 if wait: 2642 return qubx.pyenv.call_async_wait(lambda rcv: self.measure_segments(segments, table=list, row_offset=0, script_name=script_name, receiver=rcv)) 2643 else: 2644 self.measure_segments(segments, table=list, row_offset=0, script_name=script_name, receiver=receiver)
2645 - def measure_segments(self, segs, table=None, row_offset=None, script_name=None, receiver=lambda tbl, off, ct: None):
2646 """Returns (Table, row_offset, row_count), either directly if wait is True, or via receiver function.""" 2647 if self.robot.in_use or self.robot.acting: 2648 self.robot.interrupt() 2649 gobject.idle_add(self.measure_segments, segs, table, row_offset, script_name, receiver) 2650 elif segs: 2651 self.robot.in_use = True 2652 tb = self.file.segments if (table is None) else table 2653 ro = segs[0].index if (row_offset is None) else row_offset 2654 if self.tool_ruler.load_script(script_name): 2655 trouble = False 2656 try: 2657 segs, tb, ro = qubx.pyenv.env.globals['before_measure'](segs, tb, ro) 2658 except: 2659 traceback.print_exc() 2660 trouble = True 2661 if segs: # i.e. if before_measure didn't remove them all 2662 self.robot.do(self.robot_measure, segs, tb, ro, 2663 self.tool_ruler.baseline_type, self.tool_ruler.baseline_const, self.tool_ruler.baseline_rgn, 2664 trouble, receiver) 2665 self.robot.in_use = False
2666 - def measure_datasource(self, script_name=None, wait=True, receiver=lambda tbl, off, ct: None):
2667 if wait: 2668 qubx.pyenv.call_async_wait(lambda rcv: self.measure_datasource(script_name, wait=False, receiver=rcv)) 2669 return 2670 datasource = qubx.global_namespace.QubX.DataSource 2671 if datasource.source == DATASOURCE_FILE: 2672 return self.measure_segments(datasource.get_segmentation(), script_name=script_name, receiver=receiver) 2673 if datasource.source == DATASOURCE_SCREEN: 2674 self.measure(self.time.sel_left, self.time.sel_right, DATASOURCE_SCREEN, to_list=False, script_name=script_name) 2675 receiver(None, 0, 0) 2676 return 2677 if datasource.source == DATASOURCE_LIST: 2678 self.measure_list(self.file.list, script_name=script_name, wait=False, receiver=receiver)
2679 - def robot_measure(self, segs, table, row_offset, baseline_type, baseline_const, baseline_rgn, trouble, receiver):
2680 """In robot thread, measures all signals' amp,std between left and right (seconds from seg start).""" 2681 self.robot.progress = 0 2682 QubX = qubx.pyenv.env.globals['QubX'] 2683 write = QubX.write_log 2684 Nsamp = segs[0].n 2685 source_index = segs[0].signal 2686 source_segs = segs 2687 if baseline_type != RULER_BASELINE_CONST: 2688 left_ix, right_ix = [int(round(ms*1e-3/segs[0].file.sampling)) for ms in baseline_rgn] 2689 if baseline_type == RULER_BASELINE_IN_SEG: 2690 bsegs = [seg.file.get_segmentation_indexed(seg.offset + left_ix, seg.offset + right_ix, signal=seg.signal)[0] for seg in source_segs] 2691 seg_bl = dict((seg.index, seg.get_samples().samples.mean()) for seg in bsegs) 2692 elif baseline_type == RULER_BASELINE_IN_SEL: 2693 last_baseline = [None] # a way to smuggle the per-selection baseline into measured dict 2694 sigsegs = [get_segmentation_copy(source_segs, signal=s) for s in xrange(segs[0].file.signals.size)] 2695 segsigs = [ [sigseg[i] for sigseg in sigsegs] for i in xrange(len(sigsegs[0])) ] 2696 view = [v for v in QubX.Data.views if v.file == segs[0].file][0] 2697 signames = [view.signals[i, 'Name'] for i in xrange(1, view.signals.size)] 2698 def get_samples(seg, first_offset=0, last_offset=None): 2699 with self.robot.main_hold: 2700 samples = seg.get_samples(first_offset, last_offset).samples 2701 if seg.signal == source_index: 2702 if baseline_type == RULER_BASELINE_CONST: 2703 samples -= baseline_const 2704 elif baseline_type == RULER_BASELINE_IN_SEG: 2705 samples -= seg_bl[seg.index] 2706 elif baseline_type == RULER_BASELINE_IN_SEL: 2707 bseg = seg.file.get_segmentation_indexed(seg.f + left_ix, seg.f + right_ix, signal=seg.signal)[0] 2708 last_baseline[0] = bseg.get_samples().samples.mean() 2709 samples -= last_baseline[0] 2710 return samples
2711 def get_idealization(seg, first_offset=0, last_offset=None, **kw): 2712 if not ('get_fragments' in kw): 2713 kw['get_fragments'] = True 2714 kw['get_durations'] = True 2715 kw['get_amps'] = True 2716 kw['get_stds'] = False 2717 with self.robot.main_hold: 2718 firsts, lasts, classes, durations, amps = seg.get_idealization(first_offset=first_offset, last_offset=last_offset, **kw) 2719 return firsts, lasts, classes, durations, amps 2720 rows = [] 2721 for seg_ix, sigs in enumerate(segsigs): 2722 try: 2723 inclusion_mask = numpy.zeros(shape=(sigs[0].n,), dtype='int8') 2724 i = 0 2725 for chunk in sigs[0].chunks: 2726 if chunk.included: 2727 inclusion_mask[i:i+chunk.n] = 1 2728 i += chunk.n 2729 inclusion_mask = inclusion_mask.astype('bool') 2730 signals = [Anon(name=signames[sig_ix], get_samples=bind_with_args(get_samples, seg), 2731 get_idealization=bind_with_args(get_idealization, seg), 2732 hidden=self.signals[sig_ix+1,'Center'] == SIGNAL_CENTER_HIDDEN) 2733 for sig_ix, seg in enumerate(sigs)] 2734 seg = sigs[source_index] 2735 measured = {'From' : seg.f, 2736 'To' : seg.l, 2737 'Start' : seg.start, 2738 'Duration': seg.sampling * seg.n * 1e3} 2739 2740 qubx.pyenv.env.globals['measure_selection'](seg, signals, signals[source_index], inclusion_mask, measured) 2741 except: 2742 traceback.print_exc() 2743 trouble = True 2744 if baseline_type == RULER_BASELINE_CONST: 2745 measured['%s Baseline'%signals[source_index].name] = baseline_const 2746 elif baseline_type == RULER_BASELINE_IN_SEG: 2747 measured['%s Baseline'%signals[source_index].name] = seg_bl[sigs[0].index] 2748 elif baseline_type == RULER_BASELINE_IN_SEL: 2749 measured['%s Baseline'%signals[source_index].name] = last_baseline[0] 2750 rows.append(measured) 2751 self.robot.progress = seg_ix * 100.0 / len(segsigs) 2752 gobject.idle_add(self.__after_measure, segs, table, rows, row_offset, trouble, receiver)
2753 - def __after_measure(self, segs, table, rows, row_offset, trouble, receiver):
2754 for r, row in enumerate(rows): 2755 if (r + row_offset) < table.size: 2756 for k in sorted(row.keys()): 2757 if not (k in ('Index', 'From', 'To')): 2758 table[r+row_offset, k] = row[k] 2759 else: 2760 table.append(row) 2761 try: 2762 qubx.pyenv.env.globals['after_measure'](segs, table, row_offset) 2763 except: 2764 traceback.print_exc() 2765 trouble = True 2766 try: 2767 receiver(table, row_offset, len(rows)) 2768 except: 2769 traceback.print_exc() 2770 if trouble: 2771 mdlg = gtk.MessageDialog(None, buttons=gtk.BUTTONS_OK, flags=gtk.DIALOG_MODAL, 2772 message_format= "Error in measurement script. See Admin:Scripts for details.") 2773 mdlg.run() 2774 mdlg.destroy() 2775 if table == self.__screen_measurements: 2776 self.update_hint() 2777 else: 2778 view = self.QubX.Tables.find_view(table.label) 2779 if view and (view.table == table): 2780 qubx.notebook.Notebook.send(view.nbTable, auto_id='Measure.Segments')
2781 - def update_hint(self, x_=None, y_=None):
2782 x = self.__last_x if (x_ is None) else x_ 2783 y = self.__last_y if (y_ is None) else y_ 2784 self.__last_x, self.__last_y = x, y 2785 sel_info = "Selection: %.3g to %.3g ms" % (1e3*self.time.sel_left, 1e3*self.time.sel_right) 2786 if not (self.time.hilite[0] is None): 2787 sel_info = "%s\nHi-res sel: %.3g to %.3g ms" % tuple([sel_info] + [1e3*t for t in self.time.hilite]) 2788 signals = [(self.signals.get(0, 'Name'), 2789 self.x2u(x, y, self.signals.get(0, 'Center'), self.signals.get(0, 'per div')), 2790 self.signals.get(0, 'Units'))] 2791 signals += [(self.signals.get(i, 'Name'), 2792 self.y2u(y, self.signals.get(i, 'Center'), self.signals.get(i, 'per div')), 2793 self.signals.get(i, 'Units')) 2794 for i in xrange(1, self.signals.size) 2795 if self.signals[i, 'Center'] != SIGNAL_CENTER_HIDDEN] 2796 2797 hilite_info = "\n\n" 2798 if self.__bw_hint: 2799 hilite_info += "%s\n" % self.__bw_hint 2800 if self.__screen_measurements: 2801 row = self.__screen_measurements[0].__dict__ 2802 fields = self.__screen_measurements.fields[:] 2803 fields.remove('Index') 2804 fields.remove('Group') 2805 fields.sort() 2806 hilite_info += '\n'.join(['%s: %.5g' % (field, row[field]) for field in fields]) 2807 self.OnHint(self, ('%s\n\n%s\n\n' % (self.file_info.val, sel_info)) 2808 + '\n'.join(['%s:\t%12.6g %s' % sig for sig in signals]) 2809 + hilite_info)
2810 - def __show_measurements(self, table):### unused
2811 # presumably single-row 2812 fields = table.fields[:] 2813 fields.remove('Index') 2814 fields.remove('Group') 2815 fields.sort() 2816 row = table[0].__dict__ 2817 layerset = qubx.toolspace.LayerSet() 2818 layer = qubx.toolspace.Layer(x=-32, y=2, w=30, h=2*len(fields)+3) 2819 layerset.add_layer(layer) 2820 y = 0 2821 for field in fields: 2822 layer.add_sublayer(qubx.toolspace.SubLayer_Label('%s:' % field, 1, 1, x=0, y=y, w=20, h=2)) 2823 layer.add_sublayer(qubx.toolspace.SubLayer_Label('%.3g' % row[field], -1, 1, x=22, y=y, w=8, h=2)) 2824 y += 2 2825 y += .5 2826 # keep callback refs with lifetime of layer: 2827 layer.meas_copy_action = lambda x,y,e: self.__copy_measurements(table) 2828 layer.meas_hide_action = lambda x,y,e: self.__hide_measurements() 2829 layer.add_sublayer(qubx.toolspace.SubLayer_Label('Copy...', 0, 1, x=16, y=y, w=6, h=2, border=2, 2830 hover_color=qubx.toolspace.COLOR_WHITE, 2831 action=layer.meas_copy_action)) 2832 layer.add_sublayer(qubx.toolspace.SubLayer_Label('Close', 0, 1, x=23, y=y, w=6, h=2, border=2, 2833 hover_color=qubx.toolspace.COLOR_WHITE, 2834 action=layer.meas_hide_action)) 2835 if not self.__meas_restore_layerset: 2836 self.__meas_restore_layerset = self.layerset 2837 self.__meas_restore_tool = self.tool 2838 self.layerset = layerset 2839 self.tool = ShowMeasurementsTool(self.__hide_measurements) 2840 self.__timeout_measurements = gobject.timeout_add(20000, self.__hide_measurements)
2841 - def __copy_measurements(self, table):
2842 gtk.clipboard_get(gdk.SELECTION_CLIPBOARD).set_text('%s\n' % table.to_text())
2843 - def __hide_measurements(self):
2844 if self.__timeout_measurements: 2845 gobject.source_remove(self.__timeout_measurements) 2846 self.__timeout_measeurements = None 2847 if self.__meas_restore_layerset: 2848 self.layerset = self.__meas_restore_layerset 2849 self.tool = self.__meas_restore_tool 2850 self.__meas_restore_layerset = None
2851
2852 2853 2854 2855 2856 2857 2858 -def before_measure(segments, table, row_offset):
2859 return segments, table, row_offset
2860
2861 -def measure_selection(segment, signals, source_signal, inclusion_mask, measured):
2862 for signal in signals: 2863 if not signal.hidden: 2864 measure_selection_one_signal(segment, signal, inclusion_mask, measured)
2865
2866 -def measure_selection_one_signal(segment, signal, inclusion_mask, measured):
2867 samples = signal.get_samples() 2868 samples_included = samples[inclusion_mask] 2869 if len(samples_included): 2870 measured['%s Mean' % signal.name] = samples_included.mean() 2871 measured['%s Std' % signal.name] = samples_included.std() 2872 measured['%s Min' % signal.name] = samples_included.min() 2873 measured['%s Max' % signal.name] = samples_included.max() 2874 ipeak = samples_included.argmax() 2875 if samples_included[ipeak] < 0: 2876 ipeak = samples_included.argmin() 2877 measured['%s Peak_ms' % signal.name] = ipeak * segment.sampling * 1e3 2878 measured['%s Peak' % signal.name] = samples_included[ipeak] 2879 one_pct = max(1, int(round(.01 * len(samples)))) 2880 measured['%s Initial' % signal.name] = samples[:one_pct].mean() 2881 measured['%s Final' % signal.name] = samples[-one_pct:].mean()
2882
2883 -def measure_selection_idealized(segment, signal, measured):
2884 stats = qubx.fast.data.IdlStats(segment.sampling * 1e3) 2885 # use segment.chunks to find included regions; 2886 # use signal to load their samples and idealization because it's thread safe 2887 # pre-pack them for stats because they're sent thru twice 2888 stat_args = [ ([signal.get_samples(chunk.f-segment.f, chunk.l-segment.f), 2889 chunk.f] + list(signal.get_idealization(chunk.f-segment.f, chunk.l-segment.f)[:3])) 2890 for chunk in segment.chunks if (chunk.included and chunk.idealized) ] 2891 if not stat_args: 2892 return 2893 2894 for args in stat_args: 2895 stats.add(*args) 2896 stats.add_up_means() 2897 for args in stat_args: 2898 stats.add(*args) 2899 stats.add_up_stds() 2900 2901 for i, mean in enumerate(stats.get_means()): 2902 measured['Amp %i' % i] = mean 2903 for i, std in enumerate(stats.get_stds()): 2904 measured['Std %i' % i] = std 2905 for i, lifetime in enumerate(stats.get_lifetimes()): 2906 measured['Lifetime %i' % i] = lifetime 2907 for i, lifetime in enumerate(stats.get_median_lifetimes()): 2908 measured['Median Lifetime %i' % i] = lifetime 2909 for i, occupancy in enumerate(stats.get_occupancies()): 2910 measured['Occupancy %i' % i] = occupancy 2911 for i, nevent in enumerate(stats.get_nevents()): 2912 measured['N events %i' % i] = nevent 2913 for data, offset, firsts, lasts, classes in stat_args: 2914 for i, c in enumerate(classes): 2915 if c > 0: 2916 measured['First latency'] = 1e3 * segment.sampling * (firsts[i] - segment.f) 2917 break 2918 else: 2919 continue 2920 break
2921
2922 2923 -def after_measure(segments, table, row_offset):
2924 pass
2925 # if len(segments) > 1 or segments[0].index == (len(file.segmentation.segments)-1):
2926 # Notebook.send(QubX.Tables.find_view(table.label).nbTable, auto_id='Measure.Segments') 2927 2928 2929 -def minrange(lo, hi, diff):
2930 """Returns the interval (lo, hi), possibly modified to ensure (hi - lo) >= diff.""" 2931 d = max(diff, hi-lo) 2932 m = (hi+lo)/2.0 2933 return (m-d, m+d)
2934
2935 -def action_nothing(l,r):
2936 pass
2937 -def dblclick_nothing(x,y,e):
2938 pass
2939 -def scroll_nothing(x, y, e, o):
2940 pass
2941
2942 -class ShowMeasurementsTool(qubx.toolspace.Tool):
2943 - def __init__(self, on_event=lambda: None):
2944 qubx.toolspace.Tool.__init__(self) 2945 self.on_event = on_event
2946 - def onKeyPress(self, event):
2947 self.on_event()
2948 - def onRelease(self, x, y, e):
2949 self.on_event()
2950
2951 -class QubData_BaseTool(qubx.toolspace.Tool):
2952 - def onKeyPress(self, event):
2953 QubX = qubx.pyenv.env.globals['QubX'] 2954 offset = 0 2955 if event.keyval in (ord('+'), ord('='), ord('h'), ord('H'), keysyms.KP_Add): 2956 offset = 1 2957 elif event.keyval in (ord('-'), ord('_'), ord('u'), ord('U'), keysyms.KP_Subtract): 2958 offset = -1 2959 if offset: 2960 if event.state & gdk.CONTROL_MASK: 2961 qubx.pyenv.env.OnScriptable('%s.time.Gseg += %i' % (self.space.global_name, offset)) 2962 self.space.time.Gseg += offset 2963 else: 2964 if event.state & gdk.SHIFT_MASK: 2965 signal = QubX.DataSource.signal + 1 2966 else: 2967 signal = 0 # time 2968 self.space.time.defer_scriptable_scroll('%i per div' % signal, 2969 '%s.signals[%i, "per div"] = scrolled_float(QubX.Data.view.signals[%i, "per div"], %%s)' % 2970 (self.space.global_name, signal, signal), -offset, coarse=True) 2971 self.space.signals[signal, 'per div'] = scrolled_float(self.space.signals[signal, 'per div'], offset=-offset, coarse=True) 2972 return 2973 2974 if event.keyval in (keysyms.Left, keysyms.Right, keysyms.Up, keysyms.Down): 2975 if event.state & gdk.CONTROL_MASK: 2976 if event.keyval == keysyms.Left: 2977 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Iseg -= 1') 2978 self.space.time.Iseg -= 1 2979 elif event.keyval == keysyms.Right: 2980 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Iseg += 1') 2981 self.space.time.Iseg += 1 2982 elif event.keyval == keysyms.Up: 2983 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Iseg -= QubX.Data.view.time.Gseg') 2984 self.space.time.Iseg -= self.space.time.Gseg 2985 elif event.keyval == keysyms.Down: 2986 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Iseg += QubX.Data.view.time.Gseg') 2987 self.space.time.Iseg += self.space.time.Gseg 2988 elif self.space.line_count > 1: 2989 if event.keyval in (keysyms.Left, keysyms.Up): 2990 direction = -1 2991 else: 2992 direction = 1 2993 offset = direction * self.space.signals[0, 'per div'] * self.space.divs[0] # one line 2994 qubx.pyenv.env.OnScriptable('%s.signals[0, "Center"] = %s.signals[0, "Center"] + %f' % (self.space.global_name, self.space.global_name, offset)) 2995 self.space.signals[0, 'Center'] = self.space.signals[0, 'Center'] + offset 2996 else: 2997 if event.keyval in (keysyms.Left, keysyms.Right): 2998 sig_ix = 0 2999 else: 3000 sig_ix = QubX.DataSource.signal + 1 3001 center = self.space.signals[sig_ix, "Center"] 3002 per_div = self.space.signals[sig_ix, "per div"] 3003 half_screen = per_div * self.space.divs[1 if sig_ix else 0] / 2 3004 if event.state & gdk.SHIFT_MASK: 3005 offset = half_screen 3006 else: 3007 offset = per_div 3008 if event.keyval in (keysyms.Left, keysyms.Up): 3009 offset *= -1 3010 if sig_ix == 0: 3011 tmax = self.space.time.timeRange.bounds[1] 3012 if (center - half_screen) + offset < 0: 3013 offset = - (center - half_screen) 3014 elif (center + half_screen) + offset > tmax: 3015 offset = tmax - (center + half_screen) 3016 if offset: 3017 qubx.pyenv.env.OnScriptable('%s.signals[%i, "Center"] += %i * %s.signals[%i, "per div"]' % 3018 (self.space.global_name, sig_ix, int(round(offset / per_div)), self.space.global_name, sig_ix)) 3019 self.space.signals[sig_ix, "Center"] = center + offset 3020 return 3021 3022 if event.keyval in (keysyms.Home, keysyms.KP_Home): 3023 if event.state & gdk.CONTROL_MASK: 3024 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Iseg = 0') 3025 self.space.time.Iseg = 0 3026 qubx.pyenv.env.OnScriptable('t = QubX.Data.view.time.timeRange; t.set_range(0.0, t.right - t.left)') 3027 t = self.space.time.timeRange; t.set_range(0.0, t.right - t.left) ### 3028 return 3029 if event.keyval in (keysyms.End, keysyms.KP_End): 3030 if event.state & gdk.CONTROL_MASK: 3031 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Iseg = QubX.Data.view.time.Nseg - 1') 3032 self.space.time.Iseg = self.space.time.Nseg - 1 3033 qubx.pyenv.env.OnScriptable('t = QubX.Data.view.time.timeRange; t.set_range(t.bounds[1] - (t.right - t.left), t.bounds[1])') 3034 t = self.space.time.timeRange; t.set_range(t.bounds[1] - (t.right - t.left), t.bounds[1]) ### 3035 return 3036 3037 if event.keyval in (keysyms.BackSpace, keysyms.Delete): 3038 qubx.pyenv.env.OnScriptable('QubX.Data.view.hide_signal(QubX.DataSource.signal+1)') 3039 self.space.hide_signal(QubX.DataSource.signal+1) 3040 return 3041 if event.keyval == keysyms.Return: 3042 qubx.pyenv.env.OnScriptable('QubX.Data.view.center_signal(QubX.DataSource.signal+1)') 3043 self.space.center_signal(QubX.DataSource.signal+1) 3044 return 3045 # pg up/down: next/prev datasource.signal: 3046 if event.keyval in (keysyms.Page_Up, keysyms.KP_Page_Up): 3047 offset = -1 3048 elif event.keyval in (keysyms.Page_Down, keysyms.KP_Page_Down): 3049 offset = 1 3050 else: 3051 offset = 0 3052 if offset: 3053 qubx.pyenv.env.OnScriptable('QubX.DataSource.signal += %i' % offset) 3054 QubX.DataSource.signal += offset 3055 return 3056 3057 if gdk.CONTROL_MASK & event.state: 3058 if event.keyval in (ord('f'), ord('F')): # File 3059 self.space.mnuFile.popup(None, None, None, 0, event.time) 3060 return 3061 if event.keyval in (ord('g'), ord('G')): # "Go" to another file 3062 self.space.mnuFiles.action(0, 0, event) 3063 return 3064 if event.keyval in (ord('n'), ord('N')): # Notebook 3065 self.space.subNotebook.do_popup() 3066 return 3067 if event.keyval in (ord('l'), ord('L')): # Lists 3068 self.space.layLists.popup.do_popup() 3069 return 3070 else: 3071 if event.keyval in (ord('s'), ord('S')): 3072 self.space.add_screen_to_list() 3073 return 3074 3075 if event.keyval in (ord('j'), ord('J')): 3076 qubx.pyenv.env.OnScriptable('for f,l in %s.get_sels_screen(hilite=True): QubX.Data.file.ideal[QubX.DataSource.signal].idl.join(f, l)' % self.space.global_name) 3077 for f,l in self.space.get_sels_screen(hilite=True): 3078 self.space.file.ideal[QubX.DataSource.signal].idl.join(f, l) 3079 self.space.file.OnChangeIdealization(self.space.file, QubX.DataSource.signal) 3080 return 3081 3082 if event.keyval in (ord('l'), ord('L')): 3083 self.space.new_list() 3084 return 3085 if event.keyval in (ord('g'), ord('G')): 3086 qubx.pyenv.env.OnScriptable('QubX.Data.view.toggle_grid()') 3087 self.space.toggle_grid() 3088 return 3089 if event.keyval in (ord('n'), ord('N')): 3090 l, r = self.space.time.sel 3091 self.space.add_baseline_node_between(l, r) 3092 return
3093
3094 3095 3096 3097 -class QubData_HiTool(QubData_BaseTool):
3098 - def onKeyPress(self, event):
3099 QubX = qubx.pyenv.env.globals['QubX'] 3100 3101 if not (gdk.CONTROL_MASK & event.state): 3102 menu_map = [('M', self.space.subPan), 3103 ('R', self.space.subMeasure), 3104 ('V', self.space.subMeasure), 3105 ('B', self.space.subBaseline), 3106 ('X', self.space.subExclude)] 3107 menu_map.extend([(str(i), self.space.sig_layers[i].subMenu) for i in xrange(min(10, self.space.signals.size))]) 3108 for ltr, subTool in menu_map: 3109 if event.keyval in (ord(ltr), ord(ltr.lower())): 3110 subTool.action(0, 0, Anon(state=event.state, time=event.time, button=0, x=0, y=0)) 3111 return 3112 3113 if event.keyval in (ord('n'), ord('N')): 3114 l, r = self.space.time.hilite 3115 if l is None: 3116 l, r = self.space.time.sel 3117 self.space.add_baseline_node_between(l, r) 3118 return 3119 3120 # fitting? (fitting subscribes to keys too?) (need its own Tool to specialize keys?) 3121 if event.keyval in (ord('f'), ord('F')): 3122 want_fit = (self.space.layerset == self.space.controls) 3123 qubx.pyenv.env.OnScriptable('QubX.Data.view.request_fit_controls(%s)' % repr(want_fit)) 3124 self.space.request_fit_controls(want_fit) 3125 return 3126 3127 return QubData_BaseTool.onKeyPress(self, event)
3128 - def onScroll(self, x, y, event, offset):
3129 if event.state & gdk.SHIFT_MASK: 3130 offset *= 5 3131 factor = pow(1.04, abs(offset)) 3132 if offset < 0: 3133 factor = 1.0 / factor 3134 signal = qubx.global_namespace.QubX.DataSource.signal + 1 3135 center, per_div = [self.space.signals[signal, field] for field in ('Center', 'per div')] 3136 y_height = per_div * self.space.divs[1] 3137 p0 = center - y_height / 2.0 3138 p1 = center + y_height / 2.0 3139 p = self.space.y2u(y, center, per_div) 3140 p_in_frame = (p - p0) / (p1 - p0) 3141 w = (p1 - p0) / factor 3142 p0 = p - p_in_frame*w 3143 p1 = p + (1.0 - p_in_frame)*w 3144 self.space.signals[signal, 'Center'] = (p0 + p1) / 2.0 3145 self.space.signals[signal, 'per div'] = (p1 - p0) / self.space.divs[1]
3146
3147 3148 -class QubData_PanTool(QubData_HiTool):
3149 - def onPress(self, x, y, e):
3150 if not self.space.signals: 3151 return 3152 self.center, t_per_div = [self.space.signals.get(0, field) for field in ('Center', 'per div')] 3153 self.t_width = t_per_div * self.space.divs[0] 3154 self.y_signal = qubx.global_namespace.QubX.DataSource.signal + 1 3155 self.y_center, y_per_div = [self.space.signals.get(self.y_signal, field) for field in ('Center', 'per div')] 3156 self.y_height = y_per_div * self.space.divs[1] 3157 self.last_x = x 3158 self.last_y = y 3159 #self.sels[0] = self.sels[1] = x 3160 self.press_time = e.time 3161 pass ###
3162 - def onRelease(self, x, y, e):
3163 if not self.space.signals: 3164 return 3165 pass ###
3166 - def onDrag(self, x, y, e):
3167 if not self.space.signals: 3168 return 3169 dt = (self.last_x - x) * (self.t_width / self.space._dim[0]) 3170 dy = (y - self.last_y) * (self.y_height / self.space._dim[1]) 3171 self.space.signals[0, 'Center'] += dt 3172 self.space.signals[self.y_signal, 'Center'] += dy 3173 self.last_x = x 3174 self.last_y = y
3175
3176 3177 -class QubData_SelTool(QubData_HiTool):
3178 """Handles mouse interactions that involve highlighting a region of data and acting immediately. 3179 3180 @ivar color: highlight fill COLORREF 3181 @ivar action: when the user drags across a time range and lets go, action(left, right) is called 3182 @ivar dblclick: when the user double-clicks, dblclick(x, y, event) is called 3183 @ivar sels: selection end-times, possibly out of order 3184 """
3185 - def __init__(self, color=COLOR_SEL, action=action_nothing, dblclick=dblclick_nothing, scroll=None, hires_keys=True):
3186 QubData_HiTool.__init__(self) 3187 self.color = color 3188 self.__action = WeakCall("QubData_SelTool.action") 3189 self.__action.assign(action) 3190 self.__dblclick = WeakCall("QubData_SelTool.dblclick") 3191 self.__dblclick.assign(dblclick) 3192 self.__scroll = WeakCall("QubData_SelTool.scroll") 3193 self.__scroll.assign(scroll) 3194 self.hires_keys = hires_keys 3195 self.sels = [-1,-1] 3196 self.sels_y = [-1,-1]
3197 action = property(lambda self: self.__action, lambda self, x: self.__action.assign(x)) 3198 dblclick = property(lambda self: self.__dblclick, lambda self, x: self.__dblclick.assign(x)) 3199 scroll = property(lambda self: self.__scroll, lambda self, x: self.__scroll.assign(x))
3200 - def get_sel(self):
3201 """Returns the selection end-times (left, right) in order.""" 3202 if (self.space == self.space.hires) and (self.space.time.sel[1] == 0.0): 3203 return (0.0, 0.0) 3204 a,b = self.sels 3205 if (a < 0) or (a == b): 3206 return None 3207 if (a < b): 3208 return (a, b) 3209 else: 3210 return (b, a)
3211 - def get_sels(self):
3212 if (self.space == self.space.hires) and (self.space.time.sel[1] == 0.0): 3213 return (0.0, 0.0) 3214 a,b = self.sels 3215 line_height = self.space.dim[1] / self.space.line_count 3216 la, lb = [int(y/line_height) for y in self.sels_y] 3217 if (a < 0) or ((la == lb) and (a == b)): 3218 return None, None 3219 if (la < lb): 3220 return (a, b), self.sels_y 3221 if (la > lb): 3222 return (b, a), (self.sels_y[1], self.sels_y[0]) 3223 if (a < b): 3224 return (a, b), self.sels_y 3225 return (b, a), (self.sels_y[1], self.sels_y[0])
3226 - def onOverlay(self, context, w, h):
3227 if not self.space.signals: 3228 return 3229 sel, sel_y = self.get_sels() 3230 if not sel: return 3231 context.save() 3232 context.set_source_rgba(* self.space.appearance.color(self.color)) 3233 3234 a, b = sel 3235 line_height = h / self.space.line_count 3236 la, lb = [int(y/line_height) for y in sel_y] 3237 for l in xrange(la, lb+1): 3238 xa = a if (l == la) else 0 3239 xb = b if (l == lb) else w 3240 context.rectangle(xa, l*line_height, xb-xa, line_height) 3241 context.fill() 3242 context.restore()
3243 - def onPress(self, x, y, e):
3244 if not self.space.signals: 3245 return 3246 self.center, self.t_per_div = [self.space.signals.get(0, field) for field in ('Center', 'per div')] 3247 self.sels[0] = self.sels[1] = x 3248 self.sels_y[0] = self.sels_y[1] = y 3249 self.press_time = e.time
3250 - def onRelease(self, x, y, e):
3251 if not self.space.signals: 3252 return 3253 if (e.time - self.press_time) > QUBDATA_SEL_THRESH_MS: 3254 self.sels[1] = x 3255 self.sels_y[1] = y 3256 sel, sel_y = self.get_sels() 3257 if sel and sel[0]: 3258 self.action(self.space.x2u(sel[0], sel_y[0], self.center, self.t_per_div), 3259 self.space.x2u(sel[1], sel_y[1], self.center, self.t_per_div)) 3260 self.sels[0] = self.sels[1] = -1 3261 if self.space: 3262 self.space.redraw_canvas(False)
3263 - def onDblClick(self, x, y, e):
3264 if not self.space.signals: 3265 return 3266 self.dblclick(x,y,e)
3267 - def onDrag(self, x, y, e):
3268 if not self.space.signals: 3269 return 3270 self.sels[1] = x 3271 self.sels_y[1] = y 3272 self.space.redraw_canvas(False)
3273 - def onKeyPress(self, event):
3274 if self.hires_keys: 3275 return QubData_HiTool.onKeyPress(self, event) 3276 else: 3277 return QubData_BaseTool.onKeyPress(self, event)
3278 - def onScroll(self, x, y, e, o):
3279 if not self.space.signals: 3280 return 3281 if self.scroll: 3282 self.scroll(x, y, e, o) 3283 else: 3284 QubData_HiTool.onScroll(self, x, y, e, o)
3285
3286 3287 -class QubData_ExcludeTool(QubData_SelTool):
3288 - def __init__(self):
3289 self.__ref = Reffer() 3290 QubData_SelTool.__init__(self, action=self.__ref(self.do_action)) 3291 self.excluding = True 3292 self.popup = gtk.Menu() 3293 self.itemExclude = build_menuitem('Pick Exclude cursor...', self.__ref(self.__onItemExclude), item_class=gtk.CheckMenuItem, menu=self.popup) 3294 self.itemExclude.set_draw_as_radio(True) 3295 self.itemInclude = build_menuitem('Pick Include cursor...', self.__ref(self.__onItemInclude), item_class=gtk.CheckMenuItem, menu=self.popup) 3296 self.itemInclude.set_draw_as_radio(True) 3297 build_menuitem('Exclude Screen', self.__ref(self.__onItemExcludeScreen), menu=self.popup) 3298 build_menuitem('Include Screen', self.__ref(self.__onItemIncludeScreen), menu=self.popup) 3299 build_menuitem('Exclude File', self.__ref(self.__onItemExcludeFile), menu=self.popup) 3300 build_menuitem('Include File', self.__ref(self.__onItemIncludeFile), menu=self.popup)
3301 - def do_popup(self, x, y, e):
3302 self.itemExclude.set_active(self.excluding) 3303 self.itemInclude.set_active(not self.excluding) 3304 self.popup.popup(None, None, None, 0, e.time)
3305 - def __onItemExclude(self, item):
3306 self.excluding = True
3307 - def __onItemInclude(self, item):
3308 self.excluding = False
3309 - def __onItemExcludeScreen(self, item):
3310 qubx.pyenv.env.OnScriptable('for f, l in QubX.Data.view.hires.get_sels_screen(): QubX.Data.file.exclusion.exclude(f, l)') 3311 for f, l in self.space.get_sels_screen(): 3312 self.space.file.exclusion.exclude(f, l)
3313 - def __onItemIncludeScreen(self, item):
3314 qubx.pyenv.env.OnScriptable('for f, l in QubX.Data.view.hires.get_sels_screen(): QubX.Data.file.exclusion.include(f, l)') 3315 for f, l in self.space.get_sels_screen(): 3316 self.space.file.exclusion.include(f, l)
3317 - def __onItemExcludeFile(self, item):
3318 qubx.pyenv.env.OnScriptable('QubX.Data.file.exclusion.exclude_all()') 3319 self.space.file.exclusion.exclude_all()
3320 - def __onItemIncludeFile(self, item):
3321 qubx.pyenv.env.OnScriptable('QubX.Data.file.exclusion.include_all()') 3322 self.space.file.exclusion.include_all()
3323 - def do_action(self, l, r):
3324 act_on = self.excluding and self.space.file.exclusion.exclude or self.space.file.exclusion.include 3325 script_verb = self.excluding and "exclude" or "include" 3326 qubx.pyenv.env.OnScriptable('for first, last in QubX.Data.view.hires.get_sels_screen(left=%s, right=%s): QubX.Data.file.exclusion.%s(first, last)' % (l, r, script_verb)) 3327 for seg in self.space.get_segmentation(left=l, right=r, source=DATASOURCE_SCREEN): 3328 act_on(seg.f, seg.l)
3329
3330 3331 -class QubData_MeasureTool(QubData_SelTool):
3332 - def __init__(self):
3333 self.__ref = Reffer() 3334 QubData_SelTool.__init__(self, action=self.__ref(self.do_action), dblclick=self.__ref(self.do_dblclick)) 3335 self.system_path = os.path.join(qubx.pyenv.env.globals['app_path'], 'MeasurementScripts') 3336 self.user_path = os.path.join(qubx.pyenv.env.folder, 'MeasurementScripts') 3337 if not os.path.exists(self.user_path): 3338 os.makedirs(self.user_path) 3339 self.script_name = 'Default.py' 3340 self.source = DATASOURCE_SCREEN 3341 self.to_list = False 3342 self.popup = gtk.Menu() 3343 self.itemMeasureSel = build_menuitem('Measure selections...', self.__ref(self.__onItemMeasureSel), item_class=gtk.CheckMenuItem, menu=self.popup) 3344 self.itemMeasureSelToList = build_menuitem('Measure selections -> List...', self.__ref(self.__onItemMeasureSelToList), item_class=gtk.CheckMenuItem, menu=self.popup) 3345 self.itemMeasureSegments = build_menuitem('Measure selections, all segments...', self.__ref(self.__onItemMeasureSegments), item_class=gtk.CheckMenuItem, menu=self.popup) 3346 self.itemMeasureSegments.set_draw_as_radio(True) 3347 self.itemMeasureScreen = build_menuitem('Measure onscreen', self.__ref(self.__onItemMeasureScreen), menu=self.popup) 3348 self.itemMeasureSegmentsScreen = build_menuitem('Measure onscreen, all segments', self.__ref(self.__onItemMeasureSegmentsScreen), menu=self.popup) 3349 3350 self.mnuLists = gtk.Menu() 3351 build_menuitem('Measure list', submenu=self.mnuLists, menu=self.popup) 3352 self.mnuScripts = gtk.Menu() 3353 build_menuitem('Measurement script', submenu=self.mnuScripts, menu=self.popup) 3354 3355 self.layBaseline = qubx.toolspace.Layer(-53, -2.5, -8, 2) 3356 self.subBaseline = qubx.toolspace.SubLayer_Label('Meas. baseline: 0.0', 1, 1, w=-0.5, h=2, action=self.__ref(self.__onClickBaseline), 3357 tooltip="Click to choose measurement baseline options") 3358 self.layBaseline.add_sublayer(self.subBaseline) 3359 self.mnuBaseline = gtk.Menu() 3360 build_menuitem('Constant baseline...', self.__ref(self.__onClickBaselineConst), menu=self.mnuBaseline) 3361 build_menuitem('Mean of rgn in segment...', self.__ref(self.__onClickBaselineInSeg), menu=self.mnuBaseline) 3362 build_menuitem('Mean of rgn before selection...', self.__ref(self.__onClickBaselineInSel), menu=self.mnuBaseline) 3363 self.__tool_items = [] 3364 self.__baseline_type = RULER_BASELINE_CONST 3365 self.__baseline_const = 0.0 3366 self.__baseline_rgn = (0.0, 10.0)
3367
3368 - def set_baseline_type(self, x):
3369 if x == self.__baseline_type: 3370 return 3371 self.__baseline_type = x 3372 self.__update_baseline_label()
3373 baseline_type = property(lambda self: self.__baseline_type, lambda self, x: self.set_baseline_type(x))
3374 - def set_baseline_const(self, x):
3375 if x == self.__baseline_const: 3376 return 3377 self.__baseline_const = x 3378 self.__update_baseline_label()
3379 baseline_const = property(lambda self: self.__baseline_const, lambda self, x: self.set_baseline_const(x))
3380 - def set_baseline_rgn(self, x):
3381 if x == self.__baseline_rgn: return 3382 self.__baseline_rgn = x 3383 self.__update_baseline_label()
3384 baseline_rgn = property(lambda self: self.__baseline_rgn, lambda self, x: self.set_baseline_rgn(x)) 3385
3386 - def onActivate(self):
3387 QubData_SelTool.onActivate(self) 3388 self.space.redraw_canvas(True) 3389 self.space.controls.add_layer(self.layBaseline) 3390 self.space.OnDraw += self.__ref(self.__onDraw) 3391 self.space.OnDrawGL += self.__ref(self.__onDrawGL)
3392 - def onDeactivate(self):
3393 QubData_SelTool.onDeactivate(self) 3394 self.space.redraw_canvas(True) 3395 self.space.controls.remove_layer(self.layBaseline) 3396 self.space.OnDraw -= self.__ref(self.__onDraw) 3397 self.space.OnDrawGL -= self.__ref(self.__onDrawGL)
3398 - def do_popup(self, x, y, e):
3399 for item in self.__tool_items: 3400 self.popup.remove(item) 3401 self.__tool_items = [] 3402 tools = Tools.by_cat['measure'] 3403 for tool_label in sorted(tools.keys()): 3404 action, tool_class = tools[tool_label] 3405 self.__tool_items.append(build_menuitem(tool_label, action, menu=self.popup)) 3406 3407 self.mnuLists.foreach(self.mnuLists.remove) 3408 for lst in self.space.file.lists.lists: 3409 build_menuitem(lst.list_name, self.__ref(bind_with_args_before(self.__onItemMeasureList, lst)), menu=self.mnuLists) 3410 3411 self.mnuScripts.foreach(self.mnuScripts.remove) 3412 names = [] 3413 for folder in self.system_path, self.user_path: 3414 for base, dirs, files in os.walk(folder): 3415 dirs[:] = [] 3416 names.extend(fn for fn in files if fn[-3:].lower() == '.py') 3417 for name in sorted(list(set(names))): 3418 build_menuitem(name, self.__ref(bind_with_args_before(self.__onItemScript, name)), item_class=gtk.CheckMenuItem, menu=self.mnuScripts, 3419 active=(name == self.script_name)) 3420 build_menuitem('Edit script...', self.__ref(self.__onItemEditScript), menu=self.mnuScripts) 3421 3422 self.itemMeasureSel.set_active(not self.to_list) 3423 self.itemMeasureSelToList.set_active(self.to_list and (self.source == DATASOURCE_SCREEN)) 3424 self.itemMeasureSegments.set_active(self.to_list and (self.source == DATASOURCE_FILE)) 3425 3426 self.popup.popup(None, None, None, 0, e.time)
3427 - def __onItemMeasureSel(self, item):
3428 self.to_list = False 3429 self.source = DATASOURCE_SCREEN
3430 - def __onItemMeasureSelToList(self, item):
3431 self.to_list = True 3432 self.source = DATASOURCE_SCREEN
3433 - def __onItemMeasureSegments(self, item):
3434 self.to_list = True 3435 self.source = DATASOURCE_FILE
3436 - def __onItemMeasureScreen(self, item):
3437 draw_bounds = self.space.draw_bounds.val 3438 qubx.pyenv.env.OnScriptable('QubX.Data.view.measure(%s, %s, source=DATASOURCE_SCREEN, to_list=True)' % draw_bounds) 3439 self.space.measure(draw_bounds[0], draw_bounds[1], DATASOURCE_SCREEN, to_list=True, wait=False)
3440 - def __onItemMeasureSegmentsScreen(self, item):
3441 draw_bounds = self.space.draw_bounds.val 3442 qubx.pyenv.env.OnScriptable('QubX.Data.view.measure(%s, %s, source=DATASOURCE_FILE, to_list=True)' % draw_bounds) 3443 self.space.measure(draw_bounds[0], draw_bounds[1], DATASOURCE_FILE, to_list=True, wait=False)
3444 - def __onItemMeasureList(self, item, list):
3445 qubx.pyenv.env.OnScriptable('QubX.Data.view.measure_list(QubX.Data.file.lists.by_name(%s))' % repr(list.list_name)) 3446 self.space.measure_list(list, wait=False)
3447 - def __onItemScript(self, item, name):
3448 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_ruler.script_name = %s' % repr(name)) 3449 self.script_name = name
3450 - def __onItemEditScript(self, item):
3451 QubX = qubx.pyenv.env.globals['QubX'] 3452 if QubX.Admin.Scripts.scripts.promptSave(): 3453 user_script = os.path.join(self.user_path, self.script_name) 3454 if not os.path.exists(user_script): 3455 shutil.copy(os.path.join(self.system_path, self.script_name), user_script) 3456 QubX.Admin.Scripts.scripts.open(user_script) 3457 QubX.Admin.Scripts.request_show()
3458 - def do_action(self, l, r):
3459 if (self.source == DATASOURCE_SCREEN) and not self.to_list: 3460 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.set_hilite(%s, %s)' % (l, r)) 3461 self.space.time.set_hilite(l, r) 3462 else: 3463 qubx.pyenv.env.OnScriptable('QubX.Data.view.measure(%s, %s, source=%s, to_list=%s)' % 3464 (l, r, ['DATASOURCE_FILE', "DATASOURCE_SCREEN"][self.source], repr(self.to_list))) 3465 self.space.measure(l, r, self.source, self.to_list, wait=False)
3466 - def do_dblclick(self, x, y, e):
3467 t = self.space.x2u(x, y, self.space.signals[0, 'Center'], self.space.signals[0, 'per div']) 3468 time = self.space.time 3469 if (not (time.hilite[0] is None)) and (time.hilite[0] <= t <= time.hilite[1]): 3470 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.sel = QubX.Data.view.time.hilite') 3471 time.sel = time.hilite 3472 else: 3473 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.hilite = QubX.Data.view.time.sel') 3474 time.hilite = time.sel
3475 - def load_script(self, script_name=None, script_text=None):
3476 try: 3477 if not (script_name is None): 3478 path = os.path.join(self.user_path, script_name or self.script_name) 3479 if not os.path.exists(path): 3480 path = os.path.join(self.system_path, script_name or self.script_name) 3481 qubx.pyenv.env.exec_file(path, raise_exceptions=True) 3482 elif not (script_text is None): 3483 qubx.pyenv.env.exec_script(script_text) 3484 else: 3485 self.load_script(self.script_name) 3486 return True 3487 except: 3488 mdlg = gtk.MessageDialog(None, buttons=gtk.BUTTONS_OK, flags=gtk.DIALOG_MODAL, 3489 message_format= "Error in measurement script %s:\n%s" % (path, traceback.format_exc())) 3490 mdlg.run() 3491 mdlg.destroy() 3492 return False
3493 - def __update_baseline_label(self):
3494 if self.__baseline_type == RULER_BASELINE_CONST: 3495 self.subBaseline.label = 'Meas. baseline: %.3g' % self.__baseline_const 3496 elif self.__baseline_type == RULER_BASELINE_IN_SEG: 3497 self.subBaseline.label = 'Meas. baseline: mean of (%.3g, %.3g) ms in segment' % self.__baseline_rgn 3498 else: 3499 self.subBaseline.label = 'Meas. baseline: mean of (%.3g, %.3g) ms in selection' % self.__baseline_rgn 3500 if self.space: 3501 self.space.redraw_canvas(True)
3502 - def __onClickBaseline(self, x, y, e):
3503 self.mnuBaseline.popup(None, None, None, 0, e.time)
3504 - def __onClickBaselineConst(self, item):
3505 dlg = qubx.GTK.NumEntryDialog('Measure: subtract constant baseline...', qubx.pyenv.env.globals['QubX'].Data.parent_window, 3506 'Constant to be subtracted from measurements:', self.__baseline_const, acceptFloat) 3507 response = dlg.run() 3508 dlg.destroy() 3509 if response == gtk.RESPONSE_ACCEPT: 3510 if self.__baseline_type != RULER_BASELINE_CONST: 3511 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_ruler.baseline_type = RULER_BASELINE_CONST') 3512 self.baseline_type = RULER_BASELINE_CONST 3513 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_ruler.baseline_const = %s' % dlg.value) 3514 self.baseline_const = dlg.value
3515 - def __onClickBaselineInSeg(self, item):
3516 gobject.idle_add(self.__start_baseline_in_seg) # don't remove the layer in its own click handler
3517 - def __start_baseline_in_seg(self):
3518 layerset = qubx.toolspace.LayerSet() 3519 layer = qubx.toolspace.Layer(-50, 2, 48, 4) 3520 layer.add_sublayer(qubx.toolspace.SubLayer_Label('Select some data as baseline. It will be re-measured in each segment.', 0, 1, w=48, h=2)) 3521 layer.add_sublayer(qubx.toolspace.SubLayer_Label('Cancel', 0, 1, x=-16, y=2, w=8, h=2, border=2, 3522 action=self.__ref(self.__done_baseline_in_seg))) 3523 layerset.add_layer(layer) 3524 self.restore_space = self.space 3525 self.space.layerset = layerset 3526 self.space.tool = QubData_SelTool(COLOR_RULER_BASELINE, self.__ref(self.__set_baseline_in_seg))
3527 - def __done_baseline_in_seg(self, *args):
3528 self.restore_space.tool = self 3529 self.restore_space.layerset = self.restore_space.controls 3530 self.restore_space = None
3531 - def __set_baseline_in_seg(self, left, right):
3532 self.__done_baseline_in_seg() 3533 if self.__baseline_type != RULER_BASELINE_IN_SEL: 3534 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_ruler.baseline_type = RULER_BASELINE_IN_SEG') 3535 self.baseline_type = RULER_BASELINE_IN_SEG 3536 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_ruler.baseline_rgn = (%s, %s)' % (left*1e3, right*1e3)) 3537 self.baseline_rgn = (left*1e3, right*1e3)
3538 - def __onClickBaselineInSel(self, item):
3539 QubX = qubx.pyenv.env.globals['QubX'] 3540 left, right = self.__baseline_rgn 3541 dlg = gtk.Dialog('Measure: subtract baseline each selection...', QubX.Data.parent_window, gtk.DIALOG_MODAL) 3542 pack_label('Re-measure baseline in each selection, as mean of data between:', dlg.vbox) 3543 line = pack_item(gtk.HBox(), dlg.vbox) 3544 txtLeft = pack_item(qubx.GTK.NumEntry(left), line, expand=True) 3545 pack_label(' and ', line) 3546 txtRight = pack_item(qubx.GTK.NumEntry(right), line, expand=True) 3547 pack_label(' ms', line) 3548 pack_label('(offset from beginning of each selection; negative times are earlier)', dlg.vbox, fill=False) 3549 dlg.add_button('Cancel', gtk.RESPONSE_REJECT) 3550 dlg.add_button('OK', gtk.RESPONSE_ACCEPT) 3551 response = dlg.run() 3552 dlg.destroy() 3553 if response == gtk.RESPONSE_ACCEPT: 3554 left = txtLeft.value 3555 right = txtRight.value 3556 if left > right: 3557 left, right = right, left 3558 if self.__baseline_type != RULER_BASELINE_IN_SEL: 3559 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_ruler.baseline_type = RULER_BASELINE_IN_SEL') 3560 self.baseline_type = RULER_BASELINE_IN_SEL 3561 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_ruler.baseline_rgn = (%s, %s)' % (left, right)) 3562 self.baseline_rgn = (left, right)
3563 - def __onDraw(self, context, w, h):
3564 if not self.space: return 3565 if self.baseline_type == RULER_BASELINE_CONST: 3566 signal = qubx.pyenv.env.globals['QubX'].DataSource.signal 3567 y = self.space.u2y(self.baseline_const, 0, self.space.signals[signal+1, 'Center'], self.space.signals[signal+1, 'per div']) 3568 context.set_source_rgba(*self.space.appearance.color(COLOR_RULER_BASELINE)) 3569 context.set_line_width(self.space.appearance.emsize/3.0) 3570 context.move_to(0, y) 3571 context.line_to(w, y) 3572 context.stroke() 3573 elif self.baseline_type == RULER_BASELINE_IN_SEG: 3574 left, right = [self.space.u2x(x*1e-3, self.space.signals[0, 'Center'], self.space.signals[0, 'per div']) for x in self.baseline_rgn] 3575 context.set_source_rgba(*self.space.appearance.color(COLOR_RULER_BASELINE)) 3576 context.rectangle(left, 0, right-left, h) 3577 context.fill()
3578 - def __onDrawGL(self, gldrawable, glcontext, w, h):
3579 if not self.space: return 3580 if self.baseline_type == RULER_BASELINE_CONST: 3581 signal = qubx.pyenv.env.globals['QubX'].DataSource.signal 3582 y = self.space.u2y(self.baseline_const, 0, self.space.signals[signal+1, 'Center'], self.space.signals[signal+1, 'per div']) 3583 OpenGL.GL.glColor4f(*self.space.appearance.color(COLOR_RULER_BASELINE)) 3584 OpenGL.GL.glLineWidth(self.space.appearance.emsize/2.0) 3585 OpenGL.GL.glBegin(OpenGL.GL.GL_LINES) 3586 OpenGL.GL.glVertex3f(0, y, 0) 3587 OpenGL.GL.glVertex3f(w, y, 0) 3588 OpenGL.GL.glEnd() 3589 elif self.baseline_type == RULER_BASELINE_IN_SEG: 3590 left, right = [self.space.u2x(x*1e-3, self.space.signals[0, 'Center'], self.space.signals[0, 'per div']) for x in self.baseline_rgn] 3591 OpenGL.GL.glColor4f(*self.space.appearance.color(COLOR_RULER_BASELINE)) 3592 OpenGL.GL.glBegin(OpenGL.GL.GL_QUADS) 3593 OpenGL.GL.glVertex3f(left, 0, 0) 3594 OpenGL.GL.glVertex3f(left, h, 0) 3595 OpenGL.GL.glVertex3f(right, h, 0) 3596 OpenGL.GL.glVertex3f(right, 0, 0) 3597 OpenGL.GL.glEnd()
3598 3599 3600 CLONE_MODE_SOURCE, CLONE_MODE_DEST, CLONE_MODE_RESTORE = range(3)
3601 3602 -class QubData_CloneTool(QubData_SelTool):
3603 - def __init__(self):
3604 self.__ref = Reffer() 3605 QubData_SelTool.__init__(self, action=self.__ref(self.do_action)) 3606 self.mode = CLONE_MODE_SOURCE
3607 - def do_popup(self, x, y, e):
3608 overlays = self.space.file.overlays[qubx.global_namespace.QubX.DataSource.signal] 3609 self.popup = gtk.Menu() 3610 self.popup.ref = Reffer() 3611 self.itemDefine = build_menuitem('Select source data...', self.popup.ref(self.__onItemDefine), item_class=gtk.CheckMenuItem, menu=self.popup) 3612 self.itemDefine.set_draw_as_radio(True) 3613 self.itemDefine.set_active(self.mode == CLONE_MODE_SOURCE) 3614 self.itemClone = build_menuitem('Select destination(s)...', self.popup.ref(self.__onItemClone), item_class=gtk.CheckMenuItem, menu=self.popup) 3615 self.itemClone.set_draw_as_radio(True) 3616 self.itemClone.set_active(self.mode == CLONE_MODE_DEST) 3617 self.itemClone.set_sensitive(bool(overlays and (overlays.cls >= 0))) 3618 self.itemRestore = build_menuitem('Restore selections...', self.popup.ref(self.__onItemRestore), item_class=gtk.CheckMenuItem, menu=self.popup) 3619 self.itemRestore.set_draw_as_radio(True) 3620 self.itemRestore.set_active(self.mode == CLONE_MODE_RESTORE) 3621 self.itemRestore.set_sensitive(bool(overlays and (overlays.cls >= 0))) 3622 self.itemRestoreAll = build_menuitem('Restore all...', self.popup.ref(self.__onItemRestoreAll), menu=self.popup) 3623 self.itemRestoreAll.set_sensitive(bool(overlays and (overlays.cls >= 0))) 3624 if overlays and overlays.undo.can_undo: 3625 item = build_menuitem('Undo %s' % overlays.undo.undo_lbl, self.popup.ref(bind(self.__undo, overlays)), menu=self.popup) 3626 if overlays and overlays.undo.can_redo: 3627 item = build_menuitem('Redo %s' % overlays.undo.redo_lbl, self.popup.ref(bind(overlays.undo.redo)), menu=self.popup) 3628 ### clear all this signal (dlg: are you sure?) 3629 self.popup.popup(None, None, None, 0, e.time)
3630 - def __undo(self, overlays):
3631 overlays.undo.undo() 3632 if overlays.cls < 0: 3633 self.defining = True
3634 - def __onItemDefine(self, item):
3635 self.mode = CLONE_MODE_SOURCE
3636 - def __onItemClone(self, item):
3637 self.mode = CLONE_MODE_DEST
3638 - def __onItemRestore(self, item):
3640 - def __onItemRestoreAll(self, item):
3641 overlays = self.space.file.overlays[qubx.global_namespace.QubX.DataSource.signal] 3642 if overlays is None: return 3643 if qubx.GTK.PromptChoices("Remove all clone editing from this signal? This can't be undone."): 3644 overlays.clear()
3645 - def do_action(self, l, r):
3646 overlays = self.space.file.get_overlays(qubx.global_namespace.QubX.DataSource.signal) 3647 act_on = [overlays.set_source, overlays.add_dest, overlays.restore][self.mode] 3648 script_verb = ["set_source", "add_dest", "restore"][self.mode] 3649 qubx.pyenv.env.OnScriptable('for first, last in QubX.Data.view.hires.get_sels_screen(left=%s, right=%s): QubX.Data.file.overlays[QubX.DataSource.signal].%s(first, last)' % (l, r, script_verb)) 3650 segs = self.space.get_segmentation(left=l, right=r, source=DATASOURCE_SCREEN) 3651 act_on(segs[0].f, segs[0].l) 3652 if self.mode == CLONE_MODE_SOURCE: 3653 self.mode = CLONE_MODE_DEST
3654 - def onActivate(self):
3655 QubData_HiTool.onActivate(self) 3656 self.space.mark_overlays = False
3657 - def onDeactivate(self):
3658 QubData_HiTool.onDeactivate(self) 3659 self.space.mark_overlays = True
3660
3661 3662 -class QubData_BaselineTool(QubData_HiTool):
3663 """Edits baseline nodes; draws them as an overlay."""
3664 - def __init__(self):
3665 QubData_HiTool.__init__(self) 3666 self.__ref = Reffer() 3667 self.sels = [-1,-1] 3668 self.__sel_clears = False 3669 self.mouse_clears = False 3670 self.__sel_seg_bl = False 3671 self.__sel_file_bl = False 3672 self.popup = gtk.Menu() 3673 self.itemAddNodes = build_menuitem('Add baseline nodes...', self.__ref(self.__onClickAddNodes), item_class=gtk.CheckMenuItem, menu=self.popup) 3674 self.itemClearNodes = build_menuitem('Clear selected nodes...', self.__ref(self.__onClickClearNodes), item_class=gtk.CheckMenuItem, menu=self.popup) 3675 build_menuitem('Clear onscreen nodes', self.__ref(self.__onClickClearOnscreen), menu=self.popup) 3676 build_menuitem('Clear all nodes', self.__ref(self.__onClickClearAll), menu=self.popup) 3677 self.itemSelFileBaseline = build_menuitem('Select whole file baseline...', self.__ref(self.__onClickSelFileBaseline), menu=self.popup, item_class=gtk.CheckMenuItem) 3678 self.itemSelSegBaseline = build_menuitem('Select segment baseline...', self.__ref(self.__onClickSelSegBaseline), menu=self.popup, item_class=gtk.CheckMenuItem) 3679 self.mnuSegBaseline = gtk.Menu() 3680 build_menuitem('Segment baseline', submenu=self.mnuSegBaseline, menu=self.popup) 3681 self.itemUndoSegBaseline = build_menuitem('Undo', self.__ref(self.__onClickUndoSegBaseline), menu=self.mnuSegBaseline) 3682 self.itemRedoSegBaseline = build_menuitem('Redo', self.__ref(self.__onClickRedoSegBaseline), menu=self.mnuSegBaseline) 3683 build_menuitem('Edit...', self.__ref(self.__onClickEditSegBaseline), menu=self.mnuSegBaseline) 3684 self.dotmap = qubx.fast.clickmap.DotMap() 3685 self.dot_points = []
3686 sel_seg_bl = property(lambda self: self.__sel_seg_bl, lambda self, x: self.set_sel_seg_bl(x)) 3687 sel_file_bl = property(lambda self: self.__sel_file_bl, lambda self, x: self.set_sel_file_bl(x))
3688 - def set_sel_seg_bl(self, x):
3689 self.__sel_seg_bl = x 3690 self.__sel_file_bl = False
3691 - def set_sel_file_bl(self, x):
3692 self.__sel_seg_bl = False 3693 self.__sel_file_bl = x
3694 - def onActivate(self):
3697 - def onDeactivate(self):
3700 - def set_sel_clears(self, x):
3701 self.__sel_clears = x 3702 self.sel_seg_bl = False 3703 self.sel_file_bl = False
3704 sel_clears = property(lambda self: self.__sel_clears, lambda self, x: self.set_sel_clears(x))
3705 - def get_sel(self):
3706 """Returns the selection end-times (left, right) in order.""" 3707 a,b = self.sels 3708 if (a < 0) or (a == b): 3709 return None 3710 if (a < b): 3711 return (a, b) 3712 else: 3713 return (b, a)
3714 - def onChangeBaselineNodes(self, signal):
3715 if self.space: 3716 self.space.redraw_canvas(False)
3717 - def onOverlay(self, context, w, h):
3718 self.__update_signal() 3719 QubX = qubx.pyenv.env.globals['QubX'] 3720 # nodes and lines 3721 self.dot_points = [] 3722 node_x = [] 3723 node_y = [] 3724 context.set_source_rgba(* qubx.toolspace.SETALPHA(self.space.appearance.color(COLOR_BASELINE_NODES), 0.8)) 3725 context.set_line_width(0.4 * self.space.appearance.emsize) 3726 seg = self.space.get_segmentation_screen(signal=QubX.DataSource.signal)[0] # first seg only 3727 baseline = self.space.file.baseline[QubX.DataSource.signal] 3728 t2x = self.space.u2x 3729 u2y = self.space.u2y 3730 brange = baseline.range_at(seg.f) 3731 def visit(node): 3732 delta = max(1, (seg.l - seg.f + 1) / (w/3)) # to fight rounding errors with display bounds -- multi-line modulo tricks get confused in scope.u2x 3733 p = max(seg.f+delta, min(seg.l-delta, node.point)) 3734 x = t2x((p - seg.offset) * self.space.file.sampling, self.center, self.t_per_div) 3735 v = baseline.interpolate(p) 3736 y = u2y(v, 0, self.center_y, self.u_per_div) 3737 self.dot_points.append(p) 3738 node_x.append(x) 3739 node_y.append(y) 3740 return p, v, x, y
3741 if brange: 3742 p, v, x, y = visit(brange.node_a) 3743 context.move_to(x, y) 3744 while brange and (brange.node_a.point <= seg.l): 3745 p, v, x, y = visit(brange.node_b) 3746 context.line_to(x, y) 3747 if (len(node_x) + 1) % 1024: 3748 context.stroke() 3749 context.move_to(x, y) 3750 brange = brange.next() 3751 context.set_source_rgba(* self.space.appearance.color(COLOR_BASELINE_NODES)) 3752 self.dotrad = 0.6 * self.space.appearance.emsize 3753 for x,y in izip(node_x, node_y): 3754 context.arc(x, y, self.dotrad, 0, 2*pi) 3755 context.fill() 3756 self.dotmap.reset(node_x, node_y) 3757 3758 # drag-highlight 3759 sel = self.get_sel() 3760 if not sel: return 3761 context.save() 3762 a, b = sel 3763 base_color = qubx.toolspace.SETALPHA(self.space.appearance.color(COLOR_BASELINE_NODES), 0.6) 3764 if self.mouse_clears: 3765 base_color = INVERT_COLOR(base_color) 3766 context.set_source_rgba(* base_color) 3767 context.rectangle(a, 0, b-a, h) 3768 context.fill() 3769 context.restore()
3770 - def __update_signal(self):
3771 QubX = qubx.pyenv.env.globals['QubX'] 3772 self.center, self.t_per_div = [self.space.signals.get(0, field) for field in ('Center', 'per div')] 3773 self.signal = QubX.DataSource.signal 3774 self.center_y, self.u_per_div = [self.space.signals.get(self.signal+1, field) for field in ('Center', 'per div')]
3775 - def onPress(self, x, y, e):
3776 self.__update_signal() 3777 self.mouse_clears = self.sel_clears or (e.state & gdk.CONTROL_MASK) 3778 self.sels[0] = self.sels[1] = x 3779 self.press_time = e.time 3780 self.press_point = (x, y) 3781 self.press_timeout_handler = gobject.timeout_add(QUBDATA_SEL_THRESH_MS, self.__onTimeout, x, y, Anon(button=e.button, time=e.time)) 3782 self.press_node_ix = self.dotmap.find(x, y, self.dotrad) if not (self.sel_seg_bl or self.sel_file_bl) else -1 3783 self.drag_pt_val = (None, None)
3784 - def __onTimeout(self, x, y, e):
3785 self.press_timeout_handler = None 3786 if self.press_node_ix >= 0: 3787 self.space.release_button(e) # triggers node popup
3788 - def __cancel_timeout(self):
3789 if self.press_timeout_handler: 3790 gobject.source_remove(self.press_timeout_handler) 3791 self.press_timeout_handler = None
3792 - def onDrag(self, x, y, e):
3793 self.mouse_clears = self.sel_clears or (e.state & gdk.CONTROL_MASK) 3794 if self.press_timeout_handler: 3795 px, py = self.press_point 3796 if ((px - x)**2 + (py - y)**2) > (self.space.appearance.emsize**2): 3797 self.__cancel_timeout() 3798 if not self.press_timeout_handler: 3799 if self.press_node_ix >= 0: 3800 self.drag_pt_val = (self.dot_points[self.press_node_ix], self.space.y2u(y, self.center_y, self.u_per_div)) 3801 # move it up and down 3802 self.space.file.baseline[self.signal].add_node(*self.drag_pt_val) 3803 else: 3804 self.sels[1] = x 3805 self.space.redraw_canvas(False)
3806 - def onRelease(self, x, y, e):
3807 self.mouse_clears = self.sel_clears or (e.state & gdk.CONTROL_MASK) 3808 if e.button == 3: 3809 self.__cancel_timeout() 3810 if self.press_node_ix >= 0: 3811 self.do_node_popup(self.press_node_ix, x, y, e) 3812 return 3813 if self.press_timeout_handler: 3814 self.__cancel_timeout() 3815 if self.sel_seg_bl or self.sel_file_bl: 3816 pass # quick click prob. an accident? 3817 elif self.mouse_clears: 3818 if self.press_node_ix >= 0: 3819 point = self.dot_points[self.press_node_ix] 3820 qubx.pyenv.env.OnScriptable('QubX.Data.file.baseline[QubX.DataSource.signal].clear_nodes(%i, %i)' % (point, point)) 3821 self.space.file.baseline[self.signal].clear_nodes(point, point) 3822 elif self.press_node_ix < 0: 3823 # quick click adds arbitrary node... (each onscreen seg) 3824 px, py = self.press_point 3825 ux = self.space.x2u(px, py, self.center, self.t_per_div) 3826 uy = self.space.y2u(py, self.center_y, self.u_per_div) 3827 iseg = self.space.time.Iseg 3828 isample = self.space.file.segmentation.segments[iseg][0] + int(round(ux/self.space.file.sampling)) 3829 qubx.pyenv.env.OnScriptable('QubX.Data.file.baseline[QubX.DataSource.signal].add_node(%i, %s)' % (isample, repr(uy))) 3830 self.space.file.baseline[self.signal].add_node(isample, uy) 3831 elif self.press_node_ix < 0: 3832 self.sels[1] = x 3833 sel = self.get_sel() 3834 if sel: 3835 t0, t1 = (self.space.x2u(sel[0], 0, self.center, self.t_per_div), 3836 self.space.x2u(sel[1], 0, self.center, self.t_per_div)) 3837 seg = self.space.get_segmentation(self.space.time.Iseg, left=t0, right=t1, signal=self.signal, baseline_nodes=False)[0] 3838 if self.sel_seg_bl: 3839 value = seg.get_samples().samples.mean() 3840 field_name = "Baseline offset %s" % self.space.file.signals[qubx.global_namespace.QubX.DataSource.signal, 'Name'] 3841 if field_name in self.space.file.segments.fields: 3842 value += self.space.file.segments[self.space.time.Iseg, field_name] 3843 qubx.pyenv.env.OnScriptable('QubX.Data.file.segments[QubX.Data.view.time.Iseg, %s] = %s' % 3844 (repr(field_name), repr(value))) 3845 self.space.file.segments[self.space.time.Iseg, field_name] = value 3846 elif self.sel_file_bl: 3847 value = seg.get_samples().samples.mean() 3848 field_name = "Baseline offset %s" % self.space.file.signals[qubx.global_namespace.QubX.DataSource.signal, 'Name'] 3849 if field_name in self.space.file.segments.fields: 3850 value += self.space.file.segments[self.space.time.Iseg, field_name] 3851 qubx.pyenv.env.OnScriptable('for iseg in xrange(QubX.Data.file.segments.size):\n QubX.Data.file.segments[iseg, %s] = %s' % 3852 (repr(field_name), repr(value))) 3853 for iseg in xrange(self.space.file.segments.size): 3854 self.space.file.segments[iseg, field_name] = value 3855 elif self.mouse_clears: 3856 qubx.pyenv.env.OnScriptable('QubX.Data.file.baseline[QubX.DataSource.signal].clear_nodes(%i, %i)' % (seg.f, seg.l)) 3857 self.space.file.baseline[self.signal].clear_nodes(seg.f, seg.l) 3858 else: 3859 # mean node 3860 point = int(round((seg.f + seg.l) / 2.0)) 3861 value = seg.get_samples().samples.mean() 3862 qubx.pyenv.env.OnScriptable('QubX.Data.file.baseline[QubX.DataSource.signal].add_node(%i, %s)' % (point, repr(value))) 3863 self.space.file.baseline[self.signal].add_node(point, value) 3864 else: 3865 pt, val = self.drag_pt_val 3866 if pt is None: 3867 self.do_node_popup(self.press_node_ix, x, y, e) 3868 else: 3869 qubx.pyenv.env.OnScriptable('QubX.Data.file.baseline[QubX.DataSource.signal].add_node(%i, %s)' % (pt, repr(val))) 3870 self.sels[0] = self.sels[1] = -1 3871 self.press_node_ix = -1 3872 if self.space: 3873 self.space.redraw_canvas(False)
3874 - def onNeedPopup(self, x, y, e):
3875 # already handling this manually (this event replaces onRelease when it's r-button or ctrl-) 3876 self.onRelease(x, y, e)
3877 - def do_popup(self, x, y, e):
3878 self.__update_signal() 3879 self.itemAddNodes.set_active((not (self.sel_seg_bl or self.sel_file_bl)) and not self.sel_clears) 3880 self.itemClearNodes.set_active((not (self.sel_seg_bl or self.sel_file_bl)) and self.sel_clears) 3881 self.itemSelSegBaseline.set_active(self.sel_seg_bl) 3882 self.itemSelFileBaseline.set_active(self.sel_file_bl) 3883 self.itemUndoSegBaseline.set_sensitive(self.space.file.seg_bl_undo[self.signal][self.space.time.Iseg].can_undo) 3884 self.itemRedoSegBaseline.set_sensitive(self.space.file.seg_bl_undo[self.signal][self.space.time.Iseg].can_redo) 3885 self.popup.popup(None, None, None, 0, e.time)
3886 - def __onClickAddNodes(self, item):
3887 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_baseline.sel_clears = False') 3888 self.sel_clears = False
3889 - def __onClickClearNodes(self, item):
3890 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_baseline.sel_clears = True') 3891 self.sel_clears = True
3892 - def __onClickClearOnscreen(self, item):
3893 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_baseline.clear_onscreen()') 3894 self.clear_onscreen()
3895 - def clear_onscreen(self):
3896 if self.dot_points: 3897 self.space.file.baseline[self.signal].clear_nodes(self.dot_points[0], self.dot_points[-1])
3898 - def __onClickClearAll(self, item):
3899 qubx.pyenv.env.OnScriptable('QubX.Data.file.baseline[QubX.DataSource.signal].clear_all()') 3900 self.space.file.baseline[self.signal].clear_all()
3901 - def __onClickSelSegBaseline(self, item):
3902 qubx.pyenv.env.OnScriptable('QubX.Data.view.tool_baseline.sel_seg_bl = True') 3903 self.sel_seg_bl = True
3904 - def __onClickSelFileBaseline(self, item):
3905 qubx.pyenv.env.OnScriptable('QubX.Data.view.tool_baseline.sel_file_bl = True') 3906 self.sel_file_bl = True
3907 - def __onClickUndoSegBaseline(self, item):
3908 self.space.file.seg_bl_undo[self.signal][self.space.time.Iseg].undo()
3909 - def __onClickRedoSegBaseline(self, item):
3910 self.space.file.seg_bl_undo[self.signal][self.space.time.Iseg].redo()
3911 - def __onClickEditSegBaseline(self, item):
3912 field = 'Baseline offset %s' % self.space.file.signals[qubx.global_namespace.QubX.DataSource.signal, 'Name'] 3913 if not field in self.space.file.segments.fields: 3914 self.space.file.segments.add_field(field, 0.0, acceptFloat, '%.3g', '') 3915 self.space.file.segments.select(self.space.time.Iseg, field, sender=self.space)
3916 - def do_node_popup(self, press_node_ix, x, y, e):
3917 point = self.dot_points[press_node_ix] 3918 value = self.space.file.baseline[self.signal].interpolate(point) 3919 menu = gtk.Menu() 3920 build_menuitem('Baseline node:', menu=menu) 3921 for field, val in [('index', '%i' % point), 3922 ('value', '%.3g %s' % (value, self.space.signals[self.signal+1, 'Units']))]: 3923 build_menuitem(' %s: %s' % (field, val), menu=menu) 3924 build_menuitem('Remove node', self.__ref(bind_with_args_before(self.__onClickRemove, press_node_ix)), menu=menu) 3925 menu.popup(None, None, None, 0, e.time)
3926 - def __onClickRemove(self, item, press_node_ix):
3927 point = self.dot_points[press_node_ix] 3928 qubx.pyenv.env.OnScriptable('QubX.Data.file.baseline[QubX.DataSource.signal].clear_nodes(%i, %i)' % (point, point)) 3929 self.space.file.baseline[self.signal].clear_nodes(point, point)
3930
3931 3932 3933 -def draw_baseline_nodes_icon(cr, w, h, appearance):
3934 w = int(w) 3935 h = int(h) 3936 margin = min(w, h)/6 3937 cr.save() 3938 cr.set_line_width(4.0) 3939 cr.set_source_rgba(.5,.5,.5,.5) 3940 cr.rectangle(margin, margin, w-2*margin, h-2*margin) 3941 cr.stroke_preserve() 3942 cr.set_source_rgba(0,0,0,.8) 3943 cr.fill() 3944 margin += 2 3945 cr.translate(margin, margin) 3946 ptrad = max(1, (min(w,h) - 2*margin)/10.0) 3947 dx = max(1e-6, 1.0 / (w - 2*margin)) 3948 dy = max(1e-6, 1.0 / (h - 2*margin)) 3949 f = lambda x: .25 * (h-2*margin) * (x + 1) 3950 cr.set_source_rgba(1, 1, 1, .6) 3951 dy = 0.0 3952 for i,x in enumerate(numpy.random.uniform(size=3*w)): 3953 cr.arc(x/dx, ((h-2*margin) - f(x) + ptrad*random.normal()), ptrad, 0, 2*pi) 3954 cr.fill() 3955 cr.set_source_rgba(* appearance.color(COLOR_BASELINE_NODES)) 3956 ptrad = appearance.emsize * 0.3 3957 cr.set_line_width(0.65 * ptrad) 3958 cr.move_to(0, (h - 2*margin) - f(.2)) 3959 for x in (.2, .8): 3960 y = (h-2*margin) - f(x) 3961 cr.line_to(x/dx, y) 3962 cr.stroke() 3963 cr.arc(x/dx, y, ptrad, 0, 2*pi) 3964 cr.fill() 3965 cr.move_to(x/dx, y) 3966 cr.line_to(w-2*margin, (h - 2*margin) - f(.8)) 3967 cr.stroke() 3968 cr.restore()
3969
3970 3971 -class QubData_BaumWelchTool(QubData_SelTool):
3972 - def __init__(self):
3973 self.__ref = Reffer() 3974 QubData_SelTool.__init__(self, action=self.__ref(self.do_action))
3975 - def do_action(self, l, r):
3976 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.run_baum_welch(%s, %s)' % (l, r)) 3977 self.space.run_baum_welch(l, r, wait=False)
3978
3979 3980 3981 3982 -class SubLayer_DisplayMenu(qubx.toolspace.SubLayer_Icon):
3983 """Display menu."""
3984 - def __init__(self, *args, **kw):
3985 if not ('w' in kw): 3986 kw['w'] = 2.5 3987 if not ('h' in kw): 3988 kw['h'] = 2.5 3989 self.__ref = Reffer() 3990 kw['action'] = self.__ref(self.__action) 3991 qubx.toolspace.SubLayer_Icon.__init__(self, *args, **kw) 3992 self.popup = gtk.Menu() 3993 self.itemKeys = build_menuitem('Show keyboard shortcuts...', self.__ref(bind(TheKeyMessageBox.present)), menu=self.popup) 3994 self.itemGrid = build_menuitem('Show grid lines', self.__ref(self.__onClickGrid), item_class=gtk.CheckMenuItem, menu=self.popup) 3995 self.itemHideHidden = build_menuitem("Hide controls of hidden signals", self.__ref(self.__onClickHideHidden), item_class=gtk.CheckMenuItem, menu=self.popup) 3996 self.itemMultiLine = build_menuitem("Multi-line display (when top panel is large)", self.__ref(self.__onClickMultiLine), item_class=gtk.CheckMenuItem, menu=self.popup) 3997 self.itemAutoScale = build_menuitem("Auto-scale data signals", self.__ref(self.__onClickAutoScale), item_class=gtk.CheckMenuItem, menu=self.popup) 3998 self.itemColorIdealized = build_menuitem("Draw idealization with class colors", self.__ref(self.__onClickColorIdealized), item_class=gtk.CheckMenuItem, menu=self.popup) 3999 self.itemGaussIntensity = build_menuitem("Draw MacRates fit with Gaussian intensity (slower)", self.__ref(self.__onClickGaussIntensity), item_class=gtk.CheckMenuItem, menu=self.popup) 4000 build_menuitem('Colors...', self.__ref(self.__onClickColors), menu=self.popup) 4001 self.mnuPresets = gtk.Menu() 4002 build_menuitem('Signal presets', submenu=self.mnuPresets, menu=self.popup) 4003 self.__checking = False
4004 - def __action(self, x, y, e):
4005 self.do_popup(x, y, e)
4006 - def on_popup(self):
4007 self.__checking = True 4008 self.itemGrid.set_active(self.layer.space.show_grid) 4009 self.itemHideHidden.set_active(self.layer.space.appearance.hide_hidden_signals) 4010 self.itemMultiLine.set_active(self.layer.space.appearance.multi_line_data) 4011 self.itemAutoScale.set_active(self.layer.space.appearance.auto_scale_data) 4012 self.itemColorIdealized.set_active(self.layer.space.appearance.color_idealized) 4013 self.itemGaussIntensity.set_active(self.layer.space.appearance.gauss_intensity) 4014 self.__checking = False 4015 self.mnuPresets.foreach(lambda item: self.mnuPresets.remove(item)) 4016 for name in qubx.settings.SettingsMgr.listNames('Data.Display'): 4017 build_menuitem(name, self.__ref(bind_with_args_before(self.__onClickPreset, name)), menu=self.mnuPresets) 4018 build_menuitem('Manage...', self.__ref(self.__onClickManagePresets), menu=self.mnuPresets) 4019 build_menuitem('Add to menu...', self.__ref(self.__onClickAddPreset), menu=self.mnuPresets)
4020 - def do_popup(self, x, y, e):
4021 self.on_popup() 4022 self.popup.popup(None, None, None, 0, e.time)
4023 - def __onClickGrid(self, item):
4024 if self.__checking: return 4025 qubx.pyenv.env.OnScriptable('QubX.Data.view.toggle_grid()') 4026 self.layer.space.toggle_grid()
4027 - def __onClickHideHidden(self, item):
4028 if self.__checking: return 4029 hide_hidden = not self.layer.space.appearance.hide_hidden_signals 4030 qubx.pyenv.env.OnScriptable('QubX.Data.view.appearance.hide_hidden_signals = %s' % repr(hide_hidden)) 4031 self.layer.space.appearance.hide_hidden_signals = hide_hidden
4032 - def __onClickMultiLine(self, item):
4033 if self.__checking: return 4034 multi_line = not self.layer.space.appearance.multi_line_data 4035 qubx.pyenv.env.OnScriptable('QubX.Data.view.appearance.multi_line_data = %s' % repr(multi_line)) 4036 self.layer.space.appearance.multi_line_data = multi_line
4037 - def __onClickAutoScale(self, item):
4038 if self.__checking: return 4039 auto_scale = not self.layer.space.appearance.auto_scale_data 4040 qubx.pyenv.env.OnScriptable('QubX.Data.view.appearance.auto_scale_data = %s' % repr(auto_scale)) 4041 self.layer.space.appearance.auto_scale_data = auto_scale
4042 - def __onClickColorIdealized(self, item):
4043 if self.__checking: return 4044 color_idealized = not self.layer.space.appearance.color_idealized 4045 qubx.pyenv.env.OnScriptable('QubX.Data.view.appearance.color_idealized = %s' % repr(color_idealized)) 4046 self.layer.space.appearance.color_idealized = color_idealized
4047 - def __onClickGaussIntensity(self, item):
4048 if self.__checking: return 4049 gauss_intensity = not self.layer.space.appearance.gauss_intensity 4050 qubx.pyenv.env.OnScriptable('QubX.Data.view.appearance.gauss_intensity = %s' % repr(gauss_intensity)) 4051 self.layer.space.appearance.gauss_intensity = gauss_intensity
4052 - def __onClickColors(self, item):
4053 qubx.pyenv.env.globals['QubX'].Admin.Settings.show_colors_dialog()
4054 - def __onClickManagePresets(self, item):
4055 QubX = qubx.pyenv.env.globals['QubX'] 4056 dlg = qubx.settingsGTK.PresetsDialog('Data.Display', QubX.Data.parent_window, QubX.appname, '.qprs') 4057 dlg.run() 4058 dlg.destroy()
4059 - def __onClickAddPreset(self, item):
4060 dlog = qubx.GTK.InputDialog('Add preset to menu...', None, 'Preset name:', '') 4061 response = dlog.run() 4062 name = dlog.text 4063 dlog.destroy() 4064 if response == gtk.RESPONSE_ACCEPT: 4065 if name: # should check for special characters 4066 qubx.settings.SettingsMgr.save(table_to_tree(self.layer.space.signals), 'Data.Display', name)
4067 - def __onClickPreset(self, item, name):
4068 qubx.pyenv.env.OnScriptable('qubx.settings.SettingsMgr["Data.Display"].load(%s)' % repr(name)) 4069 qubx.settings.SettingsMgr['Data.Display'].load(name)
4070 - def draw(self, cr, w, h, appearance):
4071 qubx.toolspace.SubLayer_Icon.draw(self, cr, w, h, appearance) 4072 w = int(self.w) 4073 h = int(self.h) 4074 margin = max(1, min(w, h)/6) 4075 cr.save() 4076 cr.set_line_width(min(margin, 4.0)) 4077 cr.set_source_rgba(.5,.5,.5,.5) 4078 cr.rectangle(margin, margin, w-2*margin-1, h-2*margin-1) 4079 cr.stroke_preserve() 4080 cr.set_source_rgba(0,0,0,.8) 4081 cr.fill() 4082 margin += 2 4083 cr.translate(margin-0.5, margin-0.5) 4084 cr.set_line_width(1.0) 4085 cr.set_source_rgba(* qubx.scope.COLOR_GRID[1]) 4086 for i in xrange(4): 4087 x = (i/3.0) * (w-2*margin) 4088 cr.move_to(x, 0) 4089 cr.line_to(x, h-2*margin) 4090 cr.stroke() 4091 y = (i/3.0) * (h-2*margin) 4092 cr.move_to(0, y) 4093 cr.line_to(w-2*margin, y) 4094 cr.stroke()
4095
4096 4097 -class QubData_JoinTool(QubData_SelTool):
4098 tool_label = 'J_oin selected events...'
4099 - def __init__(self):
4100 self.__ref = Reffer() 4101 QubData_SelTool.__init__(self, action=self.__ref(self.do_action))
4102 - def do_action(self, l, r):
4103 sig = qubx.pyenv.env.globals['QubX'].DataSource.signal 4104 ideal = self.space.file.ideal[sig] 4105 qubx.pyenv.env.OnScriptable('for first, last in QubX.Data.view.get_sels_screen(left=%s, right=%s): QubX.Data.file.ideal[QubX.DataSource.signal].idl.join(first, last)' % (l, r)) 4106 for seg in self.space.get_segmentation(left=l, right=r, source=DATASOURCE_SCREEN): 4107 ideal.idl.join(seg.f, seg.l) 4108 self.space.file.OnChangeIdealization(self.space.file, sig)
4109 4110 Tools.register('ideal', QubData_JoinTool.tool_label, tool_class=QubData_JoinTool)
4111 4112 4113 4114 -class Layer_Lists(qubx.toolspace.Layer):
4115 - def __init__(self, *args, **kw):
4116 qubx.toolspace.Layer.__init__(self, border=4, cBorder=COLOR_LIST_BEAD, cBG=qubx.scope.COLOR_SIGNAL_BG, *args, **kw) 4117 self.__ref = Reffer() 4118 self.subList = SubLayer_List(w=-4, h=-.01) 4119 self.add_sublayer(self.subList) 4120 self.mnu = gtk.Menu() 4121 build_menuitem('New list...', self.__ref(self.__onClickNew), menu=self.mnu) 4122 build_menuitem('Chop...', self.__ref(self.__onClickChop), menu=self.mnu) 4123 build_menuitem('New list from sub-selection...', self.__ref(self.__onClickNewSubList), menu=self.mnu) 4124 self.mnuSwitch = gtk.Menu() 4125 self.itemSwitch = build_menuitem('Switch to list', submenu=self.mnuSwitch, menu=self.mnu) 4126 self.mnuRemove = gtk.Menu() 4127 self.itemRemove = build_menuitem('Remove list', submenu=self.mnuRemove, menu=self.mnu) 4128 self.__mnu_permanent = len(self.mnu.get_children()) 4129 self.popup = qubx.toolspace.SubLayer_Popup(self.mnu, color=COLOR_LIST_BEAD, tooltip='Manage lists of selections and labels', 4130 on_popup=self.__ref(self.on_popup_lists), caption='Lists', x=-5.5, w=5.5, h=self.h) 4131 self.add_sublayer(self.popup) 4132 self.tool_replace = QubData_ListReplaceTool() 4133 4134 self.__file = None
4135 - def connect_space(self, space):
4136 self.subList.connect_space(space)
4137 - def disconnect_space(self, space):
4138 self.subList.disconnect_space(space)
4139 - def set_file(self, file):
4140 if self.__file: 4141 self.subList.list = None 4142 self.__file = file 4143 if self.__file: 4144 if not self.__file.lists.lists: 4145 self.__file.lists.show_list('Notes') 4146 else: 4147 self.__file.lists.show_list(self.__file.list.list_name) 4148 self.subList.list = self.__file.list
4149 file = property(lambda self: self.__file, lambda self, x: self.set_file(x))
4150 - def on_popup_lists(self):
4151 self.mnu.ref = Reffer() 4152 for parent, mnu, action in [(self.itemSwitch, self.mnuSwitch, self.__onClickSwitch), 4153 (self.itemRemove, self.mnuRemove, self.__onClickRemove)]: 4154 mnu.foreach(lambda item: mnu.remove(item)) 4155 for i, l in enumerate(self.__file.lists.lists): 4156 build_menuitem(l.list_name, self.mnu.ref(bind(action, i)), menu=mnu) 4157 parent.set_submenu(mnu) 4158 for item in self.mnu.get_children()[self.__mnu_permanent:]: 4159 self.mnu.remove(item) 4160 list_tools = Tools.by_cat['list'] 4161 for tool_label in sorted(list_tools.keys()): 4162 action, tool_class = list_tools[tool_label] 4163 build_menuitem(tool_label, action, menu=self.mnu) 4164 subMeasure = gtk.Menu() 4165 build_menuitem('Measure (pick script):', menu=self.mnu, submenu=subMeasure) 4166 names = [] 4167 hires = self.space 4168 try: 4169 hires = self.space.hires 4170 except: 4171 pass 4172 if hires is None: 4173 hires = qubx.global_namespace.QubX.Data.view.hires 4174 for folder in hires.tool_ruler.system_path, hires.tool_ruler.user_path: 4175 for base, dirs, files in os.walk(folder): 4176 dirs[:] = [] 4177 names.extend(fn for fn in files if fn[-3:].lower() == '.py') 4178 for name in sorted(list(set(names))): 4179 build_menuitem(name, self.mnu.ref(bind_with_args_before(self.__onItemScript, name)), menu=subMeasure) 4180 return True
4181 - def __onItemScript(self, item, name):
4182 qubx.pyenv.env.OnScriptable('QubX.Data.view.measure_list(QubX.Data.file.list, script_name=%s)' % repr(name)) 4183 qubx.global_namespace.QubX.Data.view.measure_list(self.space.file.list, script_name=name, wait=False)
4184 - def __onClickNew(self, item):
4185 self.space.new_list()
4186 - def __onClickChop(self, item):
4187 qubx.chop.Chop(silent=False)
4188 - def __onClickNewSubList(self, item):
4189 QubX = qubx.global_namespace.QubX 4190 response = qubx.GTK.PromptEntries([('This function builds a list by resizing each selection in the existing list.', None, None, None), 4191 ('Output list name:', '%s_sub'%self.space.file.list.list_name, str, str)], 4192 "New list from sub-selection", QubX.Data.parent_window) 4193 if not response: return 4194 out_list_name = response[1] 4195 if not out_list_name: return 4196 self.tool_replace.run(QubX.Data.view.hires, "Highlight a portion of one selection:", 4197 bind_with_args(self.__onSelSubList, out_list_name))
4198 - def __onSelSubList(self, out_list_name, left, right):
4199 space = qubx.global_namespace.QubX.Data.view.hires 4200 first = space.file.segmentation.segments[space.time.Iseg][0] 4201 first, last = (first + int(left / space.file.sampling), 4202 first + int(ceil(right / space.file.sampling))) 4203 sels_intersecting = space.file.list.selections_in(first, last) 4204 if not sels_intersecting: return 4205 old_first, old_last, old_label, old_index = sels_intersecting[0] 4206 offset = first - old_first 4207 count = last - first + 1 4208 max_grow = max(0, last - old_last) 4209 qubx.pyenv.env.OnScriptable("QubX.Data.file.lists.make_sub_list(name=%s, offset=%s, count=%s, max_grow=%s)" % (repr(out_list_name), offset, count, max_grow)) 4210 self.space.file.lists.make_sub_list(out_list_name, offset, count, max_grow)
4211 - def __onClickSwitch(self, i):
4212 self.__file.lists.index = i 4213 qubx.pyenv.env.globals['QubX'].Tables.show_table(self.__file.list)
4214 - def __onClickRemove(self, i):
4215 qubx.pyenv.env.OnScriptable('QubX.Data.file.lists.del_list(%i)' % i) 4216 self.__file.lists.del_list(i)
4217
4218 4219 -def ExtractList(menuitem):
4220 QubX = qubx.pyenv.env.globals['QubX'] 4221 QubX.Tools.Extract.request_show() 4222 QubX.Tools.Extract.source = qubx.extract.SOURCE_LIST
4223 Tools.register('list', 'Extract data from selections...', ExtractList)
4224 4225 -def ExcludeList(item):
4226 QubX = qubx.pyenv.env.globals['QubX'] 4227 qubx.pyenv.env.OnScriptable('for sel in QubX.Data.file.list: QubX.Data.file.exclusion.exclude(sel.From, sel.To)') 4228 exclude = QubX.Data.file.exclusion.exclude 4229 for sel in QubX.Data.file.list: 4230 exclude(sel.From, sel.To)
4231 Tools.register('list', 'Exclude selections', ExcludeList)
4232 4233 -def IncludeList(item):
4234 QubX = qubx.pyenv.env.globals['QubX'] 4235 qubx.pyenv.env.OnScriptable('for sel in QubX.Data.file.list: QubX.Data.file.exclusion.include(sel.From, sel.To)') 4236 include = QubX.Data.file.exclusion.include 4237 for sel in QubX.Data.file.list: 4238 include(sel.From, sel.To)
4239 Tools.register('list', 'Include selections', IncludeList)
4240 4241 -def BaumWelchList(item):
4242 QubX = qubx.global_namespace.QubX 4243 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.run_baum_welch_list()') 4244 QubX.Data.view.hires.run_baum_welch_list()
4245 Tools.register('list', 'Baum-Welch (AMP)', BaumWelchList) 4246 4247 4248 TOO_MANY_LIST_ITEMS_TO_DRAW = 2048
4249 4250 -class SubLayer_List(qubx.toolspace.SubLayer):
4251 - def __init__(self, *args, **kw):
4252 self.__ref = Reffer() 4253 kw['mouse_drag'] = self.__ref(self.on_mouse_drag) 4254 qubx.toolspace.SubLayer.__init__(self, *args, **kw) 4255 self.__list = None 4256 self.dotmap = qubx.fast.clickmap.DotMap() 4257 self.dotrow = [] 4258 self.x2t = self.t2x = lambda x: x 4259 self.__pressed = False 4260 self.__dotrad = 1
4261 - def connect_space(self, space):
4262 space.time.OnChangeShowing += self.__ref(self.__onEdit) 4263 space.time.OnChangeSel += self.__ref(self.__onEdit)
4264 - def disconnect_space(self, space):
4265 self.x2t = self.t2x = lambda x: x 4266 space.time.OnChangeShowing -= self.__ref(self.__onEdit) 4267 space.time.OnChangeSel -= self.__ref(self.__onEdit)
4268 - def set_list(self, x):
4269 if not (self.__list is None): 4270 for event in (self.__list.OnInsert, self.__list.OnRemoved, self.__list.OnSet, self.__list.OnChangeListName, self.__list.OnSelect, self.__list.OnChecked): 4271 event -= self.__ref(self.__onEdit) 4272 self.__list = x 4273 if not (self.__list is None): 4274 for event in (self.__list.OnInsert, self.__list.OnRemoved, self.__list.OnSet, self.__list.OnChangeListName, self.__list.OnSelect, self.__list.OnChecked): 4275 event += self.__ref(self.__onEdit) 4276 self.invalidate()
4277 list = property(lambda self: self.__list, lambda self, x: self.set_list(x))
4278 - def draw(self, context, w, h, appearance):
4279 qubx.toolspace.SubLayer.draw(self, context, w, h, appearance) 4280 if self.__pressed and (self.__press_ix < 0): 4281 press_x, press_y = self.__press_coord 4282 x, y = self.__at_coord 4283 left, right = sorted([press_x, x]) 4284 context.set_source_rgba(*appearance.color(COLOR_LIST)) 4285 context.rectangle(left, 0, right-left, h) 4286 context.fill() 4287 space = self.layer.space 4288 lxpp, lypp = space._layer_scale 4289 center, per_div = space.signals.get(0, 'Center'), space.signals.get(0, 'per div') 4290 self.x2t = lambda x: space.x2u(x/lxpp, 0, center, per_div) 4291 self.t2x = lambda t: lxpp*space.u2x(t, center, per_div) 4292 xx, yy = [], [] 4293 y = self.__dotrad = h/2 4294 self.dotrow = [] 4295 if not (self.__list is None): 4296 context.save() 4297 context.set_source_rgba(*appearance.color(COLOR_LIST_NAME)) 4298 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents() 4299 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(self.__list.list_name) 4300 vtotal = fascent + fdescent 4301 by = (self.h - vtotal)/2 + fascent 4302 bx = (self.w - width) - xbearing - 5*appearance.emsize 4303 context.move_to(bx, by) 4304 context.show_text(self.__list.list_name) 4305 4306 context.set_font_size(int(round(.5*h))) 4307 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents() 4308 vtotal = fascent + fdescent 4309 segs = space.get_segmentation_screen() 4310 if segs: 4311 seg = segs[0] 4312 sels = self.__list.selections_in(seg.f, seg.l, trimmed=True) 4313 if len(sels) < TOO_MANY_LIST_ITEMS_TO_DRAW: 4314 for From, To, Label, Index in sels: 4315 if Index == space.list_sel: 4316 context.set_source_rgba(*appearance.color(COLOR_LIST_SEL)) 4317 elif self.__list.checked[Index]: 4318 context.set_source_rgba(*appearance.color(COLOR_LIST_CHECKED)) 4319 else: 4320 continue 4321 x0 = self.t2x(space.file.sampling*(From - seg.offset)) 4322 x1 = self.t2x(space.file.sampling*(To - seg.offset)) 4323 context.rectangle(x0, 0, x1-x0, self.h) 4324 context.fill() 4325 for From, To, Label, Index in self.__list.selections_in(seg.f, seg.l, trimmed=True): 4326 x = self.t2x(space.file.sampling*(From - seg.offset)) 4327 xx.append(x) 4328 yy.append(y) 4329 context.set_source_rgba(*appearance.color(COLOR_LIST_BEAD)) 4330 context.arc(x, y, .72*y, 0, 2*pi) 4331 context.fill() 4332 bead_lbl = str(Index) 4333 context.set_source_rgba(*appearance.color(COLOR_LIST_BEAD_LABEL)) 4334 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(bead_lbl) 4335 by = (self.h - vtotal)/2 + fascent 4336 bx = (x - width/2) - xbearing 4337 context.move_to(bx, by) 4338 context.show_text(bead_lbl) 4339 self.dotrow.append(Index) 4340 context.restore() 4341 self.dotmap.reset(xx, yy)
4342 - def button_press(self, x, y, e):
4343 self.__pressed = True 4344 self.__press_ix = self.dotmap.find(x, y, self.__dotrad) 4345 self.__press_coord = x, y 4346 self.__at_coord = self.__press_coord
4347 - def button_release(self, x, y, e):
4348 space = self.layer.space 4349 if self.__press_ix >= 0: 4350 rel_ix = self.dotmap.find(x, y, self.__dotrad) 4351 if self.__press_ix == rel_ix: 4352 if (e.button == 3) or (e.state & gdk.CONTROL_MASK): 4353 sel = self.__list.get_row(self.dotrow[rel_ix]) 4354 menu = gtk.Menu() 4355 build_menuitem(' %i: %s' % (self.dotrow[rel_ix], sel.Label), menu=menu) 4356 build_menuitem(' %i - %i' % (sel.From, sel.To), menu=menu) 4357 sub = gtk.Menu() 4358 build_menuitem('Change Group (%i)' % sel.Group, submenu=sub, menu=menu) 4359 for i in xrange(16): 4360 item = build_menuitem(str(i), self.__ref(bind(self.__onClickChangeGroup, sel.Index, i)), menu=sub) 4361 if i == sel.Group: 4362 item.set_sensitive(False) 4363 build_menuitem('Other...', self.__ref(bind(self.__onClickChangeGroup, sel.Index)), menu=sub) 4364 build_menuitem('Replace selection...', self.__ref(bind(self.__replace_sel, self.dotrow[rel_ix])), menu=menu) 4365 build_menuitem('Remove selection', self.__ref(bind(self.__remove_sel, self.dotrow[rel_ix])), menu=menu) 4366 build_menuitem(self.__list.checked[self.dotrow[rel_ix]] and 'Un-check row' or 'Check row', 4367 self.__ref(bind(self.__check_sel, self.dotrow[rel_ix])), menu=menu) 4368 menu.popup(None, None, None, 0, e.time) 4369 else: 4370 qubx.pyenv.env.globals['QubX'].Tables.show_table(self.__list) 4371 self.__list.select(self.dotrow[rel_ix], sender=self.layer.space) 4372 elif ((space != space.hires) or (space.time.sel[0] > 0)) and (not (self.__list is None)) and self.__list.user_can_edit: 4373 press_x, press_y = self.__press_coord 4374 left, right = sorted([self.x2t(press_x), self.x2t(x)]) 4375 first = space.file.segmentation.segments[space.time.Iseg][0] 4376 first, last = (first + int(left / space.file.sampling), 4377 first + int(ceil(right / space.file.sampling))) 4378 qubx.pyenv.env.OnScriptable('QubX.Data.file.list.insert_selection(From=%i, To=%i)' % (first, last)) 4379 ix = self.__list.insert_selection(first, last) 4380 qubx.pyenv.env.globals['QubX'].Tables.show_table(self.__list) 4381 self.__list.select(ix, 'Label', sender=self.layer.space) 4382 self.__pressed = False 4383 self.invalidate()
4384 - def __onClickChangeGroup(self, index, group=None):
4385 if group is None: 4386 grp = qubx.pyenvGTK.prompt_entry('Change to group:', self.__list[index, 'Group'], acceptIntGreaterThanOrEqualTo(0)) 4387 if grp is None: 4388 return 4389 else: 4390 grp = group 4391 qubx.pyenv.env.OnScriptable('QubX.Data.file.list[%i, "Group"] = %i' % (index, grp)) 4392 self.__list[index, 'Group'] = grp
4393 - def __remove_sel(self, index):
4394 qubx.pyenv.env.OnScriptable('QubX.Data.file.list.remove(%i)' % index) 4395 self.__list.remove(index)
4396 - def __replace_sel(self, index, left=None, right=None):
4397 space = qubx.global_namespace.QubX.Data.view.hires 4398 if not (space.file.list and space.file.list.user_can_edit): 4399 return 4400 if left is None: 4401 self.layer.tool_replace.run(space, "Highlight a selection to replace #%i:" % index, bind_with_args(self.__replace_sel, index)) 4402 else: 4403 first = space.file.segmentation.segments[space.time.Iseg][0] 4404 first, last = (first + int(left / space.file.sampling), 4405 first + int(ceil(right / space.file.sampling))) 4406 lbl = space.file.list[index, "Label"] 4407 qubx.pyenv.env.OnScriptable('QubX.Data.file.list.remove(%i)\nQubX.Data.file.list.insert_selection(%i, %i, Label=%s)' % (index, first, last, repr(lbl))) 4408 space.file.list.remove(index) 4409 space.file.list.insert_selection(first, last, lbl)
4410 - def __check_sel(self, index):
4411 x = not self.__list.checked[index] 4412 qubx.pyenv.env.OnScriptable('QubX.Data.file.list.set_checked(%i, %s)' % (index, repr(x))) 4413 self.__list.set_checked(index, x)
4414 - def on_mouse_drag(self, x, y, e):
4415 self.__at_coord = x, y 4416 self.invalidate()
4417 - def __onEdit(self, *args):
4418 self.invalidate()
4419
4420 -class QubData_ListReplaceTool(QubData_SelTool):
4421 - def __init__(self):
4422 self.__ref = Reffer() 4423 QubData_SelTool.__init__(self, action=self.__ref(self.do_action)) 4424 self.layerset = qubx.toolspace.LayerSet() 4425 self.layer = qubx.toolspace.Layer(1, 1, 40, 4.5, cBG=qubx.scope.COLOR_SIGNAL_BG) 4426 self.layerset.add_layer(self.layer) 4427 self.subsCaption = [] 4428 self.subCaption = qubx.toolspace.SubLayer_Label('', -1, 1, x=0.5, y=0.5, w=-0.5, h=1.5, 4429 color=COLOR_DATA_LAYER_FG, hover_color=COLOR_DATA_LAYER_FG) 4430 self.layer.add_sublayer(self.subCaption) 4431 self.layer.add_sublayer(qubx.toolspace.SubLayer_Label('Cancel', 0, 1, x=30, y=2.5, w=8, h=1.7, border=1, 4432 color=COLOR_DATA_LAYER_FG, hover_color=COLOR_DATA_LAYER_HOVER, 4433 action=self.__ref(bind(self.dismiss))))
4434 - def run(self, space, caption, do_replace):
4435 self.prev_layerset = space.layerset 4436 self.prev_tool = space.tool 4437 self.do_replace = do_replace 4438 self.subCaption.label = caption 4439 space.layerset = self.layerset 4440 space.tool = self
4441 - def dismiss(self):
4442 self.space.layerset = self.prev_layerset 4443 self.space.tool = self.prev_tool 4444 self.prev_tool = self.prev_layerset = self.do_replace = None
4445 - def do_action(self, l, r):
4446 do_replace = self.do_replace 4447 self.dismiss() 4448 do_replace(l, r)
4449
4450 4451 #class QubDataCopyDialog(qubx.GTK.CopyDialog): 4452 # """Modal window with copy data image settings.""" 4453 # def __init__(self, parent=None): 4454 # qubx.GTK.CopyDialog.__init__(self, 'Copy data image', parent) 4455 # self.chkOverlays = gtk.CheckButton('overlays') 4456 # self.chkOverlays.set_active(True) 4457 # self.chkOverlays.show() 4458 # self.vbox.pack_start(self.chkOverlays, False, True) 4459 # def run(self, view): 4460 # response = qubx.GTK.CopyDialog.run(self, view) 4461 # self.copy_overlays = self.chkOverlays.get_active() 4462 # return response 4463 # 4464 #TheDataCopyDialog = QubDataCopyDialog() 4465 4466 4467 -class DataSource(object):
4468 """Provides sampled and idealized data to the user's specification. 4469 4470 @ivar source: DATASOURCE_FILE, DATASOURCE_SCREEN, DATASOURCE_LIST 4471 @ivar signal: zero-based index of active signal 4472 @ivar OnChangeSource: L{WeakEvent}(source) 4473 @ivar OnChangeSignal: L{WeakEvent}(signal) 4474 @ivar OnChangeSamples: L{WeakEvent}() 4475 @ivar OnChangeIdealization: L{WeakEvent}() 4476 @ivar OnChangeSel: L{WeakEvent}() # when mouse selection changes 4477 """
4478 - def __init__(self, qubx):
4479 self.__ref = Reffer() 4480 self.qubx = qubx 4481 self.datas = self.qubx.Data 4482 self.datas.OnSwitch += self.__ref(self.__onSwitchData) 4483 self.__data = None 4484 gobject.idle_add(self.__onSwitchData, self.datas, self.datas.file) 4485 self.__source = DATASOURCE_SCREEN 4486 self.__signal = 0 4487 self.signals = ['Current'] 4488 self.OnChangeSource = WeakEvent() # (source) 4489 self.OnChangeSignal = WeakEvent() # (signal) 4490 self.OnChangeSignals = WeakEvent() # ([signal_name]) 4491 self.OnChangeSamples = WeakEvent() # () 4492 self.OnChangeIdealization = WeakEvent() # () 4493 self.OnChangeSel = WeakEvent()
4494 - def set_source(self, source):
4495 if self.__source != source: 4496 self.__source = source 4497 self.OnChangeSource(source) 4498 self.__onChangeData()
4499 source = property(lambda self: self.__source, lambda self, x: self.set_source(x))
4500 - def set_signal(self, signal):
4501 if self.__data and (self.__signal != signal) and (signal < self.data.file.signals.size): 4502 self.__signal = signal 4503 self.OnChangeSignal(signal) 4504 self.__onChangeData() 4505 self.__onChangeSel() 4506 elif not self.data: 4507 self.__signal = signal 4508 self.OnChangeSignal(signal)
4509 signal = property(lambda self: self.__signal, lambda self, x: self.set_signal(x))
4510 - def __onSwitchData(self, datas, data):
4511 self.data = datas.view
4512 - def set_data(self, data):
4513 if data == self.__data: return 4514 if self.__data and self.__data.signals: 4515 self.__data.time.OnChangeSel -= self.__ref(self.__onChangeSel) 4516 self.__data.time.OnChangeHilite -= self.__ref(self.__onChangeHilite) 4517 self.__data.file.OnChangeSamples -= self.__ref(self.__onChangeSamples) 4518 self.__data.file.OnChangeIdealization -= self.__ref(self.__onChangeIdealization) 4519 self.__data.signals.OnInsert -= self.__ref(self.__onChangeSignals) 4520 self.__data.signals.OnRemoved -= self.__ref(self.__onChangeSignals) 4521 self.__data.signals.OnSet -= self.__ref(self.__onSetSignals) 4522 self.__data.file.lists.OnSwitchList -= self.__ref(self.__onChangeList) 4523 self.__data.file.lists.OnChangeList -= self.__ref(self.__onChangeList) 4524 self.__data = data 4525 if self.__data and self.__data.signals: 4526 self.__data.time.OnChangeSel += self.__ref(self.__onChangeSel) 4527 self.__data.time.OnChangeHilite += self.__ref(self.__onChangeHilite) 4528 self.__data.file.OnChangeSamples += self.__ref(self.__onChangeSamples) 4529 self.__data.file.OnChangeIdealization += self.__ref(self.__onChangeIdealization) 4530 self.__data.signals.OnInsert += self.__ref(self.__onChangeSignals) 4531 self.__data.signals.OnRemoved += self.__ref(self.__onChangeSignals) 4532 self.__data.signals.OnSet += self.__ref(self.__onSetSignals) 4533 self.__data.file.lists.OnSwitchList += self.__ref(self.__onChangeList) 4534 self.__data.file.lists.OnChangeList += self.__ref(self.__onChangeList) 4535 self.__onChangeSignals() 4536 self.__onChangeData() 4537 self.__onChangeSel()
4538 data = property(lambda self: self.__data, lambda self, x: self.set_data(x))
4539 - def __onChangeSel(self, *args):
4540 self.OnChangeSel() 4541 if self.source != DATASOURCE_SCREEN: return 4542 self.__onChangeSamples() 4543 self.__onChangeIdealization()
4544 - def __onChangeHilite(self, *args):
4545 self.OnChangeSel()
4546 - def __onChangeList(self, *args):
4547 if self.source != DATASOURCE_LIST: return 4548 self.__onChangeSamples() 4549 self.__onChangeIdealization()
4550 - def __onChangeSignals(self, *args):
4551 if (not self.__data) or (not self.__data.signals): 4552 self.signals = ['Current'] 4553 else: 4554 ss = self.__data.signals 4555 self.signals = [ss.get(i, 'Name') for i in xrange(1, ss.size)] 4556 self.OnChangeSignals(self.signals)
4557 - def __onSetSignals(self, i, field_name, val, prev, undoing):
4558 if field_name == 'Name': 4559 ss = self.__data.signals 4560 self.signals = [ss.get(i, 'Name') for i in xrange(1, ss.size)] 4561 self.OnChangeSignals(self.signals)
4562 - def __onChangeData(self, *args):
4563 if self.data and self.data.signals and not (0 <= self.signal < self.data.file.signals.size): 4564 self.signal = 0 4565 else: # the next 2 are called in set_signal anyway 4566 self.__onChangeSamples() 4567 self.__onChangeIdealization()
4568 - def __onChangeSamples(self, *args):
4569 self.OnChangeSamples()
4570 - def __onChangeIdealization(self, *args):
4571 self.OnChangeIdealization()
4572 - def get_segmentation_files(self, views=None, group=None):
4573 """Returns a list of (L{QubDataView}, segm) for each QubDataView in views (or all open files), 4574 where segm = view.get_segmentation_file().""" 4575 vws = views or self.qubx.Data.views 4576 if not (group is None): 4577 vws = [v for v in vws if v.file.group == group] 4578 return [(v, v.get_segmentation_file()) for v in vws]
4579 - def get_segmentation_file(self, dataview=None, signal=None, latency=None, baseline_nodes=True):
4580 """Returns a list of L{SourceSeg} corresponding to the whole file.""" 4581 return self.get_segmentation(dataview, 0, len(self.data.file.segmentation.segments)-1, 4582 self.data.time.timeRange.bounds[0], self.data.time.timeRange.bounds[1], 4583 signal, latency, baseline_nodes=baseline_nodes)
4584 - def get_segmentation_screen(self, dataview=None, signal=None, latency=None, baseline_nodes=True):
4585 """Returns a list of L{SourceSeg} corresponding to what's onscreen.""" 4586 if self.data.time.sel_left != self.data.time.sel_right: 4587 return self.get_segmentation(dataview, self.data.time.Iseg, self.data.time.Iseg + self.data.time.Greal - 1, 4588 self.data.time.sel_left, self.data.time.sel_right, signal, latency, baseline_nodes=baseline_nodes) 4589 else: 4590 return self.get_segmentation(dataview, self.data.time.Iseg, self.data.time.Iseg + self.data.time.Greal - 1, 4591 self.data.time.left, self.data.time.right, signal, latency, baseline_nodes=baseline_nodes)
4592 - def get_segmentation_list(self, dataview=None, signal=None, group=None):
4593 return (dataview or self.data).get_segmentation_list(signal=signal, group=group)
4594 - def get_segmentation(self, dataview=None, first_seg=None, last_seg=None, left=None, right=None, signal=None, latency=None, baseline_nodes=True):
4595 """Returns a list of L{SourceSeg} corresponding to source, modified by args. 4596 4597 @param dataview: L{QubDataView}, or None for the file onscreen 4598 @param first_seg: 4599 @param last_seg: 4600 @param left: time offset from start-of-each-seg 4601 @param right: time offset from start-of-each-seg 4602 @param signal: index of data signal 4603 @param latency: shift the data to the right by this many samples 4604 @param baseline_nodes: True to subtract baseline nodes 4605 """ 4606 data = dataview or self.data 4607 if not data: 4608 return [] 4609 sig = self.signal if (signal is None) else signal 4610 if self.source == DATASOURCE_LIST: 4611 return data.file.get_segmentation_list(signal=sig, baseline_nodes=baseline_nodes) 4612 else: 4613 return data.get_segmentation(first_seg, last_seg, left, right, self.source, sig, latency, baseline_nodes=baseline_nodes)
4614 - def gen_samples(self, chunks, main_hold=None, signal=None, maxlen=(1<<20), get_excluded=False):
4615 """Yields a L{SourceChunk} with fields .samples and .sampling for each in chunks. 4616 4617 @param chunks: list of L{SourceChunk} 4618 @param main_hold: L{qubx.task.GTK_Main_Hold} if in a worker thread, or None in the main thread 4619 @param signal: signal index, or None to use the chunk.signal 4620 @param maxlen: chunks longer than this will be yielded in pieces 4621 @param get_excluded: if True, yields sampled chunks even when included==False (if False, yields orig chunk when excluded). 4622 """ 4623 return generate_chunk_samples(chunks, main_hold, signal, maxlen, get_excluded)
4624 - def get_idealization(self, chunk, signal=None, mark_excluded=False, get_fragments=False, get_durations=False):
4625 """Returns the dwells in chunk as numpy arrays 4626 4627 @param chunk: L{SourceChunk} 4628 @param signal: signal index, or None to use the chunk.signal 4629 @param mark_excluded: if True, replaces excluded dwells with class -1 (or -2?) 4630 @param get_fragments: if False, omits first/last dwells if incomplete 4631 @param get_durations: if True, returns durations 4632 4633 @return: firsts, lasts, classes[, durations] 4634 """ 4635 return chunk.get_idealization(signal, mark_excluded, get_fragments, get_durations)
4636
4637 4638 4639 -def RequestSampling(sampling):
4640 dlg = qubx.GTK.NumEntryDialog('%s - Enter the sampling rate'%qubx.pyenv.env.globals['QubX'].appname, None, 'Sampling [kHz]:', 4641 1e-3/sampling, acceptFloatGreaterThan(0.0)) 4642 if gtk.RESPONSE_ACCEPT == dlg.run(): 4643 sampling = 1e-3 / dlg.value 4644 dlg.destroy() 4645 return sampling
4646 qubx.data_types.RequestSampling = RequestSampling
4647 4648 -def acceptBytes(s):
4649 b = int(s) 4650 if b in [2, 4, 8]: 4651 return b 4652 else: 4653 raise Exception('expected 2, 4, or 8')
4654
4655 -def RequestBinaryParams(sampling, scaling, floating, bytes, signals):
4656 dlg = qubx.GTK.NumEntriesDialog('%s - Read raw binary'%qubx.pyenv.env.globals['QubX'].appname) 4657 dlg.add_item('sampling', 'Sampling [kHz]: ', 1e-3/sampling, acceptFloatGreaterThan(0.0)) 4658 dlg.add_item('scaling', 'Scaling [int/unit]: ', scaling, acceptFloatNonzero) 4659 dlg.add_item('floating', 'Floating-point: ', floating, acceptBool, str) 4660 dlg.add_item('bytes', 'Bytes: ', bytes, acceptBytes, str) 4661 dlg.add_item('signals', 'Signal count: ', signals, acceptIntGreaterThan(0), str) 4662 if gtk.RESPONSE_ACCEPT == dlg.run(): 4663 sampling = 1e-3 / dlg.values['sampling'] 4664 scaling = dlg.values['scaling'] 4665 floating = dlg.values['floating'] 4666 bytes = dlg.values['bytes'] 4667 signals = dlg.values['signals'] 4668 dlg.destroy() 4669 return sampling, scaling, floating, bytes, signals
4670 qubx.data_types.RequestBinaryParams = RequestBinaryParams
4671 4672 4673 -def NbSubTrace(name='SubTable', fields=[]):
4674 "Returns a function(QubDataView), which returns an NbTable with only the specified columns." 4675 def make_nb(view): 4676 return qubx.notebook.NbTable(name, "QubX.Data.view.notebook[%s]" % repr(name), 4677 get_caption=view.nb_get_caption, 4678 get_shape=lambda: (view.nb_get_table_shape()[0], len([f for f in fields if f in view.fields])), 4679 get_headers=lambda: [f for f in fields if f in view.fields], 4680 get_col=lambda c: view.nb_get_col(view.fields.index([f for f in fields if f in view.fields][c])), 4681 get_col_format=lambda c: view.nb_get_col_format(view.fields.index([f for f in fields if f in view.fields][c])), 4682 get_type=lambda: float)
4683 make_nb.name = name 4684 return make_nb 4685
4686 # resulting NbTable needs global_name to reflect which panel it came from: 4687 -def NbSubTrace_Hi(name='SubTable', fields=[]):
4688 "Returns a function(QubDataView_Hi), which returns an NbTable with only the specified columns." 4689 def make_nb(view): 4690 return qubx.notebook.NbTable(name, "QubX.Data.view.hires.notebook[%s]" % repr(name), 4691 get_caption=view.nb_get_caption, 4692 get_shape=lambda: (view.nb_get_table_shape()[0], len([f for f in fields if f in view.fields])), 4693 get_headers=lambda: [f for f in fields if f in view.fields], 4694 get_col=lambda c: view.nb_get_col(view.fields.index([f for f in fields if f in view.fields][c])), 4695 get_col_format=lambda c: view.nb_get_col_format(view.fields.index([f for f in fields if f in view.fields][c])), 4696 get_type=lambda: float)
4697 make_nb.name = name 4698 return make_nb 4699
4700 4701 4702 -class KeyMessageBox(gtk.Window):
4703 - def __init__(self):
4704 gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) 4705 self.set_title('QUB Express - Keyboard shortcuts') 4706 self.connect('delete_event', self.__onDelete) 4707 scr = gtk.ScrolledWindow() 4708 scr.set_size_request(500, 600) 4709 scr.show() 4710 self.txt = gtk.TextView() 4711 self.txt.set_editable(False) 4712 scr.add(self.txt) 4713 self.txt.show() 4714 self.add(scr) 4715 out = qubx.GTK.TextViewAppender(self.txt) 4716 out.write(""" Keyboard Shortcuts 4717 4718 These keys are available when the mouse is pointing at the Data panel: 4719 4720 +, H: zoom in } 4721 -, U: zoom out } hold shift to move faster; hold ctrl to move slower 4722 Arrows: scroll } 4723 Home: go to beginning of first segment 4724 End: go to end of last segment 4725 Page Up/Down: pick previous/next signal (for Data Source and keyboard manipulation) 4726 Delete: hide signal (or ctrl-click its '?') 4727 Enter: center signal (or click its '?') 4728 0: show "Time" signal menu 4729 1-9: show signal menus 4730 4731 Ctrl-F: show file menu 4732 Ctrl-G: 'go' to another open file 4733 Ctrl-N: show notebook menu 4734 Ctrl-L: show lists menu 4735 M: pick move (scrolling) cursor 4736 R|V: pick measurement cursor 4737 B: pick baseline cursor 4738 X: pick exclusion cursor 4739 4740 F: toggle curve fitting 4741 G: toggle grid lines 4742 L: new list 4743 S: add onscreen to list 4744 N: add baseline node from onscreen data 4745 J: join all idealized onscreen events 4746 4747 Also, you can change most numbers by pointing at them and using the scroll wheel 4748 (hold Shift to go faster; hold Ctrl to go slower). 4749 4750 To assign your own keys, record and save a script using the Admin:Scripts panel, 4751 and assign that file in Admin:AltKeys. 4752 4753 Mouse tip: to zoom in, double-click on selected data. 4754 """)
4755 - def __onDelete(self, win, evt):
4756 self.hide() 4757 return True # do nothing more
4758 4759 TheKeyMessageBox = KeyMessageBox() 4760 4761 DOSERESPONSE_MEASURE_PEAK, DOSERESPONSE_MEASURE_MEAN, DOSERESPONSE_MEASURE_EQUIL = range(3) 4762 DOSERESPONSE_MAX_RESULTS = 100 4763 4764 4765 4766 @Propertied(Property('measure', DOSERESPONSE_MEASURE_PEAK, "DOSERESPONSE_MEASURE_PEAK, DOSERESPONSE_MEASURE_MEAN, or DOSERESPONSE_MEASURE_EQUIL", 4767 value_names={DOSERESPONSE_MEASURE_PEAK:"DOSERESPONSE_MEASURE_PEAK", 4768 DOSERESPONSE_MEASURE_MEAN:"DOSERESPONSE_MEASURE_MEAN", 4769 DOSERESPONSE_MEASURE_EQUIL:"DOSERESPONSE_MEASURE_EQUIL"}), 4770 Property('filter', False, 'True to apply low-pass filter to current signal'), 4771 Property('filter_kHz', 1.0, 'cutoff freq for low-pass filter'), 4772 Property('mean_dur', 10.0, 'if measure == DOSERESPONSE_MEASURE_MEAN, number of milliseconds of data to average each segment'), 4773 Property('mean_from', 500.0, 'if measure == DOSERESPONSE_MEASURE_MEAN, number of milliseconds to skip at the beginning of each segment'), 4774 Property('normalize', True, 'divides current measurements by peak current'), 4775 Property('output_name', "DoseResponse", "Name of output Table"))
4776 -class DoseResponse_Properties(gtk.Dialog):
4777 __explore_featured = ['run']
4778 - def __init__(self, parent=None):
4779 gtk.Dialog.__init__(self, 'Measure dose-response...', parent or qubx.GTK.get_active_window(), gtk.DIALOG_MODAL, 4780 buttons=(gtk.STOCK_OK, gtk.RESPONSE_ACCEPT, gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT)) 4781 self.propertied_connect_settings('qubx.dataGTK.DoseResponseProperties') 4782 h = pack_item(gtk.HBox(), self.vbox) 4783 pack_label("This dialog measures and plots ", h) 4784 self.lblVs = pack_label("", h) 4785 pack_label(", and fits to the Hill equation.", h) 4786 h = pack_item(gtk.HBox(), self.vbox) 4787 pack_label("Measure each level at:", h) 4788 h = pack_item(gtk.HBox(), self.vbox) 4789 pack_space(h, x=20) 4790 self.chkPeak = pack_radio('Peak', h) 4791 h = pack_item(gtk.HBox(), self.vbox) 4792 pack_space(h, x=20) 4793 self.chkMean = pack_radio('Mean of ', h, group=self.chkPeak) 4794 txt = pack_item(qubx.GTK.NumEntry(self.mean_from, acceptFloatGreaterThanOrEqualTo(0.0), '%.4g', width_chars=6), h) 4795 self.propertied_connect_NumEntry('mean_from', txt) 4796 pack_label('ms, starting at ', h) 4797 txt = pack_item(qubx.GTK.NumEntry(self.mean_dur, acceptFloatGreaterThan(0.0), '%.4g', width_chars=6), h) 4798 self.propertied_connect_NumEntry('mean_dur', txt) 4799 pack_label('ms', h) 4800 h = pack_item(gtk.HBox(), self.vbox) 4801 pack_space(h, x=20) 4802 self.chkEquil = pack_radio('Equilibrium', h, group=self.chkPeak) 4803 self.propertied_connect_radios('measure', [(DOSERESPONSE_MEASURE_PEAK, self.chkPeak), 4804 (DOSERESPONSE_MEASURE_MEAN, self.chkMean), 4805 (DOSERESPONSE_MEASURE_EQUIL, self.chkEquil)]) 4806 h = pack_item(gtk.HBox(), self.vbox) 4807 chk = pack_check('Filter', h) 4808 self.propertied_connect_check('filter', chk) 4809 txt = pack_item(qubx.GTK.NumEntry(self.filter_kHz, acceptFloatGreaterThan(0.0), '%.3g', width_chars=6), h) 4810 self.propertied_connect_NumEntry('filter_kHz', txt) 4811 pack_label('kHz', h) 4812 h = pack_item(gtk.HBox(), self.vbox) 4813 chk = pack_check('Normalize', h) 4814 chk.set_tooltip_text('divide all current measurements by the peak measurement, to get the fraction open') 4815 self.propertied_connect_check('normalize', chk) 4816 h = pack_item(gtk.HBox(), self.vbox) 4817 pack_label('Output Table name:', h) 4818 txt = pack_item(qubx.GTK.NumEntry(self.output_name, acceptStringNonempty), h, expand=True)
4819 - def run(self, view, model):
4820 QubX = qubx.global_namespace.QubX 4821 xser = view.signals[1+QubX.DataSource.signal, 'Name'] 4822 i, is_log = DoseResponse_FindStim(view, model) 4823 if i is None: 4824 qubx.GTK.ShowMessage("""No stimulus detected. 4825 4826 To pick a stimulus signal, first find its name in the Data panel, e.g. "Voltage." Then right-click (or long-press) a rate in the model, choose "Ligand-sensitive" or "Voltage-sensitive," and enter the signal name.""") 4827 return gtk.RESPONSE_REJECT 4828 yser = view.signals[i+1, 'Name'] 4829 if is_log: 4830 yser = 'log10(%s)' % yser 4831 self.lblVs.set_text('%s v. %s' % (yser, xser)) 4832 return gtk.Dialog.run(self)
4833
4834 4835 -def DoseResponse_FindStim(view, model):
4836 rates = model.rates 4837 ligands = [rates.get(i, 'Ligand') for i in xrange(rates.size)] 4838 volts = [rates.get(i, 'Voltage') for i in xrange(rates.size)] 4839 series = [view.signals.get(i+1, 'Name') for i in xrange(view.file.signals.size)] 4840 for i,nm in enumerate(series): 4841 if nm in ligands: 4842 return i, True 4843 elif nm in volts: 4844 return i, False 4845 return None, None
4846
4847 -def MeasureDoseResponse(measure=None, filter=None, filter_kHz=None, mean_dur=None, mean_from=None, 4848 normalize=None, output_name=None, 4849 wait=True, receiver=lambda tbl: None):
4850 """Measures current v. stimulus; plots in QubX.Charts and fits to the Hill equation. 4851 4852 @param measure: method for finding response; one of HILL_MEASURE_PEAK, HILL_MEASURE_MEAN, HILL_MEASURE_EQUIL 4853 @param filter: True to apply low-pass filter to current signal 4854 @param filter_kHz: low-pass filter freq 4855 @param mean_dur: if measure == HILL_MEASURE_MEAN, number of milliseconds of data to average each segment 4856 @param mean_from: if measure == HILL_MEASURE_MEAN, number of milliseconds to skip at the beginning of each segment 4857 @param normalize: True to divide current measurements by peak current 4858 @param output_name: name of Table for measurements 4859 @param wait: True to return the output Table upon completion; False to return immediately, and pass the Table upon completion to receiver 4860 @param receiver: function(L{qubx.table.SimpleTable}) 4861 """ 4862 if wait: 4863 return qubx.pyenv.call_async_wait(lambda rcv: MeasureDoseResponse(measure, filter, filter_kHz, mean_dur, mean_from, normalize, output_name, wait=False, receiver=rcv))[0] 4864 4865 dlg = DoseResponse_Properties() 4866 prompt = False 4867 if measure is None: 4868 prompt = True 4869 else: 4870 dlg.measure = measure 4871 if filter is None: 4872 prompt = True 4873 else: 4874 dlg.filter = filter 4875 if filter_kHz is None: 4876 prompt = True 4877 else: 4878 dlg.filter_kHz = filter_kHz 4879 if mean_from is None: 4880 prompt = True 4881 else: 4882 dlg.mean_from = mean_from 4883 if mean_dur is None: 4884 prompt = True 4885 else: 4886 dlg.mean_dur = mean_dur 4887 if normalize is None: 4888 prompt = True 4889 else: 4890 dlg.normalize = normalize 4891 QubX = qubx.global_namespace.QubX 4892 view = QubX.Data.view 4893 model = QubX.Models.file 4894 if prompt: 4895 response = dlg.run(view, model) 4896 if response != gtk.RESPONSE_ACCEPT: 4897 gobject.idle_add(receiver, None) 4898 dlg.destroy() 4899 return 4900 else: 4901 dlg.hide() 4902 4903 qubx.pyenv.env.OnScriptable('qubx.dataGTK.MeasureDoseResponse(measure=%s, filter=%s, filter_kHz=%s, mean_dur=%s, mean_from=%s, normalize=%s, output_name=%s' % (dlg.propertied_spec_dict['measure'].value_names[dlg.measure], repr(dlg.filter), repr(dlg.filter_kHz), repr(dlg.mean_dur), repr(dlg.mean_from), repr(dlg.normalize), repr(dlg.output_name))) 4904 4905 task = DoseResponse_Task(view, model, dlg.measure, dlg.filter, dlg.filter_kHz, dlg.mean_dur, dlg.mean_from, dlg.normalize, dlg.output_name, receiver) 4906 task.OnException += task_exception_to_console 4907 task.start() 4908 dlg.destroy()
4909
4910 4911 -class DoseResponse_Task(qubx.task.Task):
4912 - def __init__(self, view, model, measure, filter, filter_kHz, mean_dur, mean_from, normalize, output_name, receiver):
4913 qubx.task.Task.__init__(self, 'DoseResponse') 4914 self.view = view 4915 self.model = model 4916 self.measure = measure 4917 self.filter = filter 4918 self.filter_kHz = filter_kHz 4919 self.mean_dur = mean_dur 4920 self.mean_from = mean_from 4921 self.normalize = normalize 4922 self.output_name = output_name 4923 self.receiver = receiver 4924 QubX = qubx.global_namespace.QubX 4925 signal = QubX.DataSource.signal 4926 self.segments = QubX.DataSource.get_segmentation() 4927 self.__stop_flag = False 4928 self.__ref = Reffer() 4929 self.OnInterrupt += self.__ref(self.__onInterrupt)
4930 - def __onInterrupt(self, task, cancel):
4931 cancel() 4932 self.__stop_flag = True
4933 - def run(self):
4934 Nseg = len(self.segments) 4935 stim, is_log = DoseResponse_FindStim(self.view, self.model) 4936 segments = self.segments 4937 dose = response = None 4938 if Nseg and not (stim is None): 4939 cur = segments[0].signal 4940 if self.measure == DOSERESPONSE_MEASURE_MEAN: 4941 Ntail = segments[0].n 4942 n_sub = int(round(self.mean_dur * 1e-3 / self.view.file.sampling)) 4943 off_sub = int(round(self.mean_from * 1e-3 / self.view.file.sampling)) 4944 segments = [self.view.get_segmentation_indexed(seg.f + off_sub, min(seg.l, seg.f + off_sub + n_sub - 1), signal=cur)[0] 4945 for seg in segments] 4946 self.segments = segments 4947 else: 4948 Ntail = min(100, segments[0].n / 5) 4949 segments_stim = [self.view.get_segmentation_indexed(seg.f, seg.l, signal=stim)[0] for seg in segments] 4950 sign = 1 if (self.model.classes[1, 'Amp'] > self.model.classes[0, 'Amp']) else -1 4951 4952 if self.measure == DOSERESPONSE_MEASURE_PEAK: 4953 dr = qubx.fast.data.DoseResponse_Peak(segments[0].sampling, self.filter and self.filter_kHz or 0.0, sign) 4954 else: 4955 dr = qubx.fast.data.DoseResponse_Equil(Ntail) 4956 # qubx.fast.data.DoseResponse* want data chunks in reverse order 4957 for seg_stim, seg in izip(reversed(segments_stim), reversed(segments)): 4958 for chunk_dose, chunk_response in izip(reversed(seg_stim.chunks), reversed(seg.chunks)): 4959 if chunk_dose.included: 4960 dr.add_data(chunk_dose.get_samples().samples, chunk_response.get_samples().samples) 4961 if self.measure == DOSERESPONSE_MEASURE_PEAK: 4962 dose, response = dr.result(DOSERESPONSE_MAX_RESULTS) 4963 else: 4964 dose, response, resp_std = dr.result(DOSERESPONSE_MAX_RESULTS) 4965 if self.normalize: 4966 if sign > 0: 4967 response -= numpy.min(response) 4968 max_resp = numpy.max(response) 4969 response /= max_resp 4970 else: 4971 response -= numpy.max(response) 4972 max_resp = numpy.min(response) 4973 response /= max_resp 4974 if self.measure != DOSERESPONSE_MEASURE_PEAK: 4975 resp_std /= max_resp 4976 4977 gobject.idle_add(self.finish, stim, dose, is_log, cur, response)
4978 - def finish(self, stim, dose, is_log, cur, response):
4979 QubX = qubx.global_namespace.QubX 4980 tbl = QubX.Tables.find_table(self.output_name) or QubX.Tables.new_table(self.output_name) 4981 tbl.clear() 4982 QubX.Tables.show_table(tbl) 4983 if not (dose is None): 4984 nm_dose = self.view.signals[1+stim, "Name"] 4985 nm_response = self.view.signals[1+cur, "Name"] 4986 for d,r in izip(dose, response): 4987 tbl.append({nm_dose:d, nm_response:r}) 4988 4989 plot = QubX.Figures.Charts.add_two_plot(self.output_name, nm_dose, nm_response, is_log, False) 4990 QubX.show_charts() 4991 4992 ### is_log::: do you use the log(dose) in the Hill equation? 4993 if is_log: 4994 plot.controls.robot.set_expr('10**(x*Hill) / (10**(x*Hill) + EC50**Hill)') 4995 else: 4996 plot.controls.robot.set_expr('x**Hill / (x**Hill + EC50**Hill)') 4997 4998 plot.controls.robot.set_param('Hill', 1.0) 4999 plot.controls.robot.set_param('EC50', 1.0) 5000 plot.layerset = plot.controls 5001 plot.controls.fit() 5002 plot.controls.fit() 5003 5004 self.receiver(tbl)
5005
5006 -def task_exception_to_console(task, typ, val, tb):
5007 traceback.print_exception(typ, val, tb)
5008 5009 Tools.register('measure', 'Measure Dose-Response...', lambda item: MeasureDoseResponse(wait=False)) 5010