| Trees | Indices | Help |
|
|---|
|
|
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()
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 """
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
205 if self.__Nseg == x: return
206 self.__Nseg = x
207 if x:
208 self.setup_dur()
209 self.subSegCount.label = str(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)
216 if self.__Gseg == x: return
217 self.__Gseg = x
218 self.setup_dur()
219 self.subSegsTogether.label = str(self.__Gseg)
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)
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))
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)
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)
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")
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))
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)
332 if self.__lastSampling:
333 self.set_dur(self.__dur * sampling / self.__lastSampling)
334 self.__lastSampling = sampling
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)
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)
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)
368 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Iseg -= QubX.Data.view.time.Gseg')
369 self.Iseg = self.Iseg - self.Gseg
374 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.Iseg += QubX.Data.view.time.Gseg')
375 self.Iseg = self.Iseg + self.Gseg
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()
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()
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)
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)
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'
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
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
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[:]
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)")
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.")
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")
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")
546 mark_overlays = property(lambda self: self.__mark_overlays, lambda self, x: self.set_mark_overlays(x))
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
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)
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
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))
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))
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)
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")
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).")
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)
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))
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()
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
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
672 for tc in self.tile_cache:
673 tc.clear()
674 self.redraw_canvas(True)
675 self.file_info.invalidate()
677 self.redraw_canvas(True)
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
689 self.redraw_canvas()
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)
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)
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()
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)
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))
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]
783 qubx.scope.Scope.motion_notify(self, widget, event)
784 if not self.signals:
785 return
786 self.update_hint(event.x, event.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]))
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))
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
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()
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
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()
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
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)
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)))
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()
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()
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
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()
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()
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
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
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)
1482 segs = self.get_segmentation(left=left, right=right, source=DATASOURCE_SCREEN)
1483 return [(seg.f, seg.l) for seg in segs]
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")
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)
1501 return self.dim
1506 # to table ### TODO: segments in columns (what about non-matching excluded regions?)
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
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))
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
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)
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
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
1611 return self.nb_get_trace_series(all=True)
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))
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
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
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):
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))
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
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))
1707 i_signal = property(lambda self: self.__i_signal, lambda self, x: self.set_i_signal(x))
1712 tiles_per_seg = property(lambda self: self.__tiles_per_seg, lambda self, x: self.set_tiles_per_seg(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))
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
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)
1735 #print 'already loaded',(tile.i_seg, tile.i_left)
1736 self.__expire_tile(tile, False)
1737 return self.__heappush_tile(tile)
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
1748 tile.serial = self.serial
1749 self.serial += 1
1750 heapq.heappush(self.active_heap, (tile.serial, tile))
1751 return tile
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
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()
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:]
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)))
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
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
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
1892
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
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
1934 if self.auto_scale:
1935 self.__serial_auto_scale += 1
1936 gobject.idle_add(self.__do_auto_scale, self.__serial_auto_scale)
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
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
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
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()
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'])
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)
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
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
2074 self.do_auto_scale()
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))
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)
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)
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)
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)
2117 i = index - 1
2118 if i == -1:
2119 self.time.pop_range(True)
2120 return
2121 QubDataView_Base.center_signal(self, index)
2122
2124 return self.hires.get_segmentation_screen(signal=signal, latency=latency, baseline_nodes=baseline_nodes)
2127 - def measure_list(self, list, script_name=None, wait=True, receiver=lambda tbl, off, ct: None):
2129 - def measure_segments(self, segs, table=None, row_offset=None, script_name=None, receiver=lambda tbl, off, ct: None):
2132 return self.hires.measure_datasource(script_name=script_name, receiver=receiver)
2133
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
2153 qubx.pyenv.env.OnScriptable('QubX.Data.view.time.set_sel(%s, %s)' % (left, right))
2154 self.time.set_sel(left, right)
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)
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
2180
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
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
2310
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
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
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
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()
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()
2371
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)
2379 w = self.divs[0]*self.signals[0, 'per div']
2380 self.signals[0, 'Center'] = max(w/2.0, self.signals[0, 'Center'] - w)
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
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
2407 if not self.file: return
2408 self.subMeasure.OnHideTooltip(self.subMeasure)
2409 self.tool_ruler.do_popup(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)
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)
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)
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
2470 qubx.pyenv.env.OnScriptable('QubX.Data.view.measure_datasource(script_name="Idealized.py")')
2471 self.measure_datasource(script_name='Idealized.py', wait=False)
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)
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)
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)
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)
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):
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)
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
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()
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))
2601
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
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
2620 traceback.print_exception(typ, val, trace)
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)
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')
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)
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)
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
2859 return segments, table, row_offset
2860
2862 for signal in signals:
2863 if not signal.hidden:
2864 measure_selection_one_signal(segment, signal, inclusion_mask, measured)
2865
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
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
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
2941
2950
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
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)
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
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 ###
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
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))
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)
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])
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()
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
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)
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)
3274 if self.hires_keys:
3275 return QubData_HiTool.onKeyPress(self, event)
3276 else:
3277 return QubData_BaseTool.onKeyPress(self, event)
3285
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)
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)
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)
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)
3318 qubx.pyenv.env.OnScriptable('QubX.Data.file.exclusion.exclude_all()')
3319 self.space.file.exclusion.exclude_all()
3321 qubx.pyenv.env.OnScriptable('QubX.Data.file.exclusion.include_all()')
3322 self.space.file.exclusion.include_all()
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
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
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))
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))
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
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)
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)
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)
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)
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)
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)
3448 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_ruler.script_name = %s' % repr(name))
3449 self.script_name = name
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()
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)
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
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
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)
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
3516 gobject.idle_add(self.__start_baseline_in_seg) # don't remove the layer in its own click handler
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))
3528 self.restore_space.tool = self
3529 self.restore_space.layerset = self.restore_space.controls
3530 self.restore_space = None
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)
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)
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()
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)
3604 self.__ref = Reffer()
3605 QubData_SelTool.__init__(self, action=self.__ref(self.do_action))
3606 self.mode = CLONE_MODE_SOURCE
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)
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()
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
3660
3663 """Edits baseline nodes; draws them as an overlay."""
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))
3704 sel_clears = property(lambda self: self.__sel_clears, lambda self, x: self.set_sel_clears(x))
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)
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()
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')]
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)
3785 self.press_timeout_handler = None
3786 if self.press_node_ix >= 0:
3787 self.space.release_button(e) # triggers node popup
3789 if self.press_timeout_handler:
3790 gobject.source_remove(self.press_timeout_handler)
3791 self.press_timeout_handler = None
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)
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)
3875 # already handling this manually (this event replaces onRelease when it's r-button or ctrl-)
3876 self.onRelease(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)
3887 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_baseline.sel_clears = False')
3888 self.sel_clears = False
3890 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_baseline.sel_clears = True')
3891 self.sel_clears = True
3893 qubx.pyenv.env.OnScriptable('QubX.Data.view.hires.tool_baseline.clear_onscreen()')
3894 self.clear_onscreen()
3896 if self.dot_points:
3897 self.space.file.baseline[self.signal].clear_nodes(self.dot_points[0], self.dot_points[-1])
3899 qubx.pyenv.env.OnScriptable('QubX.Data.file.baseline[QubX.DataSource.signal].clear_all()')
3900 self.space.file.baseline[self.signal].clear_all()
3902 qubx.pyenv.env.OnScriptable('QubX.Data.view.tool_baseline.sel_seg_bl = True')
3903 self.sel_seg_bl = True
3905 qubx.pyenv.env.OnScriptable('QubX.Data.view.tool_baseline.sel_file_bl = True')
3906 self.sel_file_bl = True
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)
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)
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
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
3978
3983 """Display menu."""
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
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)
4024 if self.__checking: return
4025 qubx.pyenv.env.OnScriptable('QubX.Data.view.toggle_grid()')
4026 self.layer.space.toggle_grid()
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
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
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
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
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
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()
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)
4068 qubx.pyenv.env.OnScriptable('qubx.settings.SettingsMgr["Data.Display"].load(%s)' % repr(name))
4069 qubx.settings.SettingsMgr['Data.Display'].load(name)
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
4098 tool_label = 'J_oin selected events...'
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)
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
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))
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
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)
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))
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)
4212 self.__file.lists.index = i
4213 qubx.pyenv.env.globals['QubX'].Tables.show_table(self.__file.list)
4217
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)
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)
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)
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
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
4262 space.time.OnChangeShowing += self.__ref(self.__onEdit)
4263 space.time.OnChangeSel += self.__ref(self.__onEdit)
4265 self.x2t = self.t2x = lambda x: x
4266 space.time.OnChangeShowing -= self.__ref(self.__onEdit)
4267 space.time.OnChangeSel -= self.__ref(self.__onEdit)
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))
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)
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
4394 qubx.pyenv.env.OnScriptable('QubX.Data.file.list.remove(%i)' % index)
4395 self.__list.remove(index)
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)
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)
4418 self.invalidate()
4419
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))))
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
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
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 """
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()
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))
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))
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))
4540 self.OnChangeSel()
4541 if self.source != DATASOURCE_SCREEN: return
4542 self.__onChangeSamples()
4543 self.__onChangeIdealization()
4547 if self.source != DATASOURCE_LIST: return
4548 self.__onChangeSamples()
4549 self.__onChangeIdealization()
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)
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)
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()
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)
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
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
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
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
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
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 """)
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"))
4777 __explore_featured = ['run']
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)
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
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
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)
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)
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
5007 traceback.print_exception(typ, val, tb)
5008
5009 Tools.register('measure', 'Measure Dose-Response...', lambda item: MeasureDoseResponse(wait=False))
5010
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Dec 15 19:07:38 2017 | http://epydoc.sourceforge.net |