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()
182 self.OnChangeShowing = WeakEvent()
183 self.OnChangeSel = WeakEvent()
184 self.OnChangeHilite = WeakEvent()
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)
248 - def re_range(self, event_if_moved=True):
249 self.timeRange.set_bounds( (0.0, max(self.__dur, self.__wid)), event_if_moved )
250 if self.__sel_left >= self.__dur:
251 self.set_sel(0, 0)
252 elif self.__sel_right > self.__dur:
253 self.set_sel(self.__sel_left, self.__dur)
259 self.set_sel(x, max(x, self.__sel_right))
263 self.__sel_left = max(0, min(self.timeRange.bounds[1], left))
264 self.__sel_right = max(0, min(self.timeRange.bounds[1], right))
265 if self.__sel_left > self.timeRange.right:
266 self.right = right
267 elif self.__sel_right < self.timeRange.left:
268 self.left = left
269 self.OnChangeSel(self, left, right)
270
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
286 self.re_range(event_if_moved=False)
287
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")
329 self.__durs[:] = []
330 self.Nseg = 0
332 if self.__lastSampling:
333 self.set_dur(self.__dur * sampling / self.__lastSampling)
334 self.__lastSampling = sampling
340 self.__left = left
341 self.__right = right
342 self.__wid = right - left
343 self.__Perdiv = self.__wid / self.__Ndiv
344
345
346 self.__Center = (right + left) / 2.0
347 self.signals.set(0, 'Center', self.__Center)
348 self.signals.set(0, 'per div', self.__Perdiv)
349 gobject.idle_add( self.pop_range )
350 self.OnChangeShowing(self)
351 - def __onSetSignal(self, i, field_name, val, prev, undoing=False):
352 if i == 0:
353 if field_name == 'Center':
354 val = max(self.timeRange.quantum/2.0, min(val, self.timeRange.bounds[1]))
355 if val != self.__Center:
356 halfwid = self.__wid / 2.0
357 self.__Center = val
358 self.timeRange.set_range(val - halfwid, val + halfwid)
359 elif field_name == 'per div':
360 val = max(self.timeRange.quantum/self.__Ndiv, val)
361 if val != self.__Perdiv:
362 self.__Perdiv = val
363 self.__wid = val * self.__Ndiv
364 self.re_range(event_if_moved=False)
365 halfwid = self.__wid / 2.0
366 self.timeRange.set_range(self.__Center-halfwid, self.__Center+halfwid)
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
413 - def zoom_out(self, factor=2.0, prefer_undo=True):
417
418
419
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
449 - def __init__(self, global_name, signal_controls):
450 qubx.scope.Scope.__init__(self, global_name, signal_controls)
451 self.__ref = Reffer()
452 self.set_size_request(200, 100)
453 self.OnHint = WeakEvent()
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'
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
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)
582 raw_bounds = property(lambda self: self.__raw_bounds, lambda self, x: self.set_raw_bounds(x))
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).")
640 file = property(lambda self: self._file, lambda self,x: self.set_file(x))
641 - def __onSetSignal(self, i, field_name, val, prev, undoing=False):
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
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)
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))
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)
752 """Alternately makes grid lines in/visible."""
753 self.show_grid = not self.show_grid
763 updates = {}
764 if props.find('Row'):
765 for row in qubx.tree.children(props, 'Row'):
766 try:
767 if row['Center'].data.type == qubx.tree.QTR_TYPE_STRING:
768 updates[str(row['Name'].data)] = (float(str(row['Center'].data)), float(str(row['per div'].data)))
769 else:
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]
788 self.__hint_serial += 1
789 gobject.idle_add(self.__update_hint, self.__hint_serial, x, y)
791 if serial != self.__hint_serial:
792 return
793 sel_info = "Selection: %.3g to %.3g ms" % (1e3*self.time.sel_left, 1e3*self.time.sel_right)
794 signals = [(self.signals.get(0, 'Name'),
795 self.x2u(x, y, self.signals.get(0, 'Center'), self.signals.get(0, 'per div')),
796 self.signals.get(0, 'Units'))]
797 signals += [(self.signals.get(i, 'Name'),
798 self.y2u(y, self.signals.get(i, 'Center'), self.signals.get(i, 'per div')),
799 self.signals.get(i, 'Units'))
800 for i in xrange(1, self.signals.size)
801 if self.signals[i, 'Center'] != SIGNAL_CENTER_HIDDEN]
802 self.OnHint(self, ('%s\n\n%s\n\n' % (self.file_info.val, sel_info)) + '\n'.join(['%s:\t%12.6g %s' % sig for sig in signals]))
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
824
825
826 self._drawing = True
827
828
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)))))
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:
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:
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
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
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()
971 - def draw_list(self, context, w, h, lst, seg, spp):
972 if (not lst) or (lst.list_name == 'Segments'):
973 return
974
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)
1030 - def draw_fit(self, context, w, h, center, per_div, sig, seg):
1031 t_center, t_per_div = [self.signals.get(0, x) for x in ('Center', 'per div')]
1032 ppd_x, ppd_y = self.pix_per_div
1033 ppsec = ppd_x / t_per_div
1034 seg_n = seg.l - seg.f + 1
1035 seg_dur = seg_n * self.file.sampling
1036 seg_w = int(ceil(seg_dur * ppsec))
1037 seg_index = self.file.segmentation.index_at(seg.f)
1038 lows, highs = self.get_sampled_fits(sig, seg_index, seg.f, seg.l, seg_w)
1039 self.draw_sampled_idl(context, w, h, center, per_div, seg, lows, highs, color_inc=COLOR_FITS,
1040 gauss_intensity=self.__gauss_intensity and ((not hasattr(qubx.global_namespace.QubX, 'Modeling')) or (not hasattr(qubx.global_namespace.QubX.Modeling, 'MacRates')) or (not qubx.global_namespace.QubX.Modeling.MacRates.optimizing)))
1041 - def draw_samples(self, context, w, h, center, per_div, color, samples, dt, x0, seg_n):
1042
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
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):
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:
1069 context.set_line_width(one)
1070 for i, y in enumerate(samples):
1071 if i:
1072 context.line_to(x, y)
1073 context.stroke()
1074 context.move_to(x, y)
1075 x += dx
1076 - def draw_samples_raster(self, context, w, h, sig_color, N, lows, highs, gauss_los, gauss_his, pixels_from_start):
1077 color_tup = self.appearance.color(sig_color)
1078 context.set_source_rgba(* color_tup)
1079 cPartial = qubx.toolspace.SETALPHA(color_tup, .5*color_tup[3])
1080 context.set_line_width(1.0)
1081 context.set_source_rgba(*cPartial)
1082 for i in xrange(N):
1083 context.move_to(pixels_from_start+i, lows[i])
1084 context.line_to(pixels_from_start+i, highs[i])
1085 context.stroke()
1086 context.set_source_rgba(*color_tup)
1087 for i in xrange(N):
1088 context.move_to(pixels_from_start+i, gauss_los[i])
1089 context.line_to(pixels_from_start+i, gauss_his[i])
1090 context.stroke()
1091 - def draw_idl(self, context, w, h, center, per_div, sig, seg, offset=0):
1092 t_center, t_per_div = [self.signals.get(0, x) for x in ('Center', 'per div')]
1093 t_left = t_center - self.divs[0] * t_per_div / 2
1094 ppd_x, ppd_y = self.pix_per_div
1095 ppsec = ppd_x / t_per_div
1096 seg_n = seg.l - seg.f + 1
1097 seg_dur = seg_n * seg.file.sampling
1098 seg_w = int(ceil(seg_dur * ppsec))
1099 seg_index = self.file.segmentation.index_at(seg.f)
1100 lows, highs, class_bits = self.get_sampled_events(sig, seg_index, seg.f, seg.l, seg_w)
1101 context.save()
1102 context.translate(int(seg.file.sampling * offset * ppsec), 0)
1103 if self.signals[sig+1, 'Idl above']:
1104 cc = qubx.global_namespace.QubX.Models.file.classes
1105 context.translate(0, -(abs(cc[1,'Amp'] - cc[0,'Amp']) + cc[1,'Std'])/(per_div / ppd_y))
1106 if self.color_idealized:
1107 self.draw_color_idl(context, seg_w, h, center, per_div, seg, lows, highs, class_bits)
1108 else:
1109 self.draw_sampled_idl(context, seg_w, h, center, per_div, seg, lows, highs, gauss_intensity=False)
1110 context.restore()
1111 - def draw_sampled_idl(self, context, w, h, center, per_div, seg, lows, highs, color_inc=qubx.scope.COLOR_IDEALIZATION, color_exc=qubx.scope.COLOR_EXCLUDED, gauss_intensity=True):
1112 if not numpy.any(lows != UNSET_IDL):
1113 return
1114 dt = self.file.sampling
1115 halfwid = w / 2
1116 t_center, t_per_div = [self.signals.get(0, x) for x in ('Center', 'per div')]
1117 ppd_x, ppd_y = self.pix_per_div
1118 ppsec = ppd_x / t_per_div
1119
1120 context.save()
1121 context.translate(0, h/2)
1122 context.scale(1, -ppd_y / per_div)
1123 context.translate(0, -center)
1124 units_per_pix = per_div / ppd_y
1125
1126 context.set_line_width(self.appearance.line_width*self.appearance.emsize/8.0)
1127 chunks = seg.chunks = [seg]
1128 for chunk in chunks:
1129 if chunk.included:
1130 context.set_source_rgba(*self.appearance.color(color_inc))
1131 else:
1132 context.set_source_rgba(*self.appearance.color(color_exc))
1133 npix = len(lows)
1134 i0 = max(0, min(npix-1, int(round(npix*float(chunk.f -seg.f)/seg.n))))
1135 i1 = max(0, min(npix, int(round(npix*float(chunk.l+1-seg.f)/seg.n))))
1136 p0 = int(round((chunk.f - seg.f)*dt * ppsec))
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
1141
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))
1182 for i, lo, hi, cb in izip(count(), lows[i0:i1], highs[i0:i1], class_bits[i0:i1]):
1183 if lo != UNSET_IDL:
1184 context.move_to(p0+i, lo - units_per_pix)
1185 context.line_to(p0+i, hi + units_per_pix)
1186 if chunk.included:
1187 for c in xrange(32):
1188 if (cb & (1 << c)):
1189 context.set_source_rgba(*SETALPHA(self.appearance.color(COLOR_CLASS(c)), 0.62))
1190 context.stroke_preserve()
1191 context.new_path()
1192 else:
1193 context.stroke()
1194 context.restore()
1195 - def __onDrawGL(self, gldrawable, glcontext, w, h):
1196 if not self.file: return
1197 if not self.signals: return
1198 draw_bounds = self.draw_bounds.val
1199 if draw_bounds[0] == draw_bounds[1]: return
1200 t_per_div = self.signals.get(0, 'per div')
1201 if not t_per_div: return
1202 self.data_overlay.invalidate()
1203 self._drawing = True
1204
1205
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)))))
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
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:
1234 for chunk in chunks:
1235 samples, dt = chunk.samples, chunk.sampling
1236 sig_color = chunk.included and qubx.scope.COLOR_SIGNAL(self.signals.get(i+1, 'Name')) or qubx.scope.COLOR_EXCLUDED
1237 if seg_n < 4*w:
1238 self.draw_samples_gl(gldrawable, glcontext, w, h, center, per_div, sig_color, samples, dt, dx*(chunk.f-seg.f), seg_n)
1239 else:
1240 samples_per_pixel = self.signals.get(0,'per div') / (dt*self.pix_per_div[0])
1241 resolution = per_div / self.pix_per_div[1] * self.appearance.emsize / 4.0
1242 i0 = max(0, min(seg_w-1, int(round(seg_w*float(chunk.f-seg.f)/seg_n))))
1243 i1 = max(0, min(seg_w, int(round(seg_w*float(chunk.l-seg.f+1)/seg_n))))
1244 lows, highs, gauss_los, gauss_his = qubx.fast.data.raster_samples(i1-i0, samples, samples_per_pixel, resolution)
1245 self.draw_samples_raster_gl(gldrawable, glcontext, w, h, center, per_div, sig_color, lows, highs, gauss_los, gauss_his,
1246 int(round(dx * (chunk.f-seg.f))), vline_vertices)
1247 self.draw_idl_gl(gldrawable, glcontext, w, h, center, per_div, i, chunk, chunk.f-seg.f, vline_vertices)
1248 else:
1249 self.draw_idl_gl(gldrawable, glcontext, w, h, center, per_div, i, chunks, 0, vline_vertices)
1250 self._drawing = False
1251 - def draw_list_gl(self, gldrawable, glcontext, w, h, lst, seg, spp):
1252 if not lst: return
1253 def rect(x0, y0, x1, y1, draw_type=OpenGL.GL.GL_QUADS):
1254 OpenGL.GL.glBegin(draw_type)
1255 OpenGL.GL.glVertex3f(x0, y0, 0)
1256 OpenGL.GL.glVertex3f(x0, y1, 0)
1257 OpenGL.GL.glVertex3f(x1, y1, 0)
1258 OpenGL.GL.glVertex3f(x1, y0, 0)
1259 OpenGL.GL.glEnd()
1260
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
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
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
1284 samples -= center
1285 samples *= - ppd_y / per_div
1286 samples += h/2
1287
1288 color_tup = self.appearance.color(color)
1289 OpenGL.GL.glColor4f(* color_tup)
1290 OpenGL.GL.glLineWidth(one)
1291 OpenGL.GL.glEnable(OpenGL.GL.GL_LINE_SMOOTH)
1292 OpenGL.GL.glEnable(OpenGL.GL.GL_POLYGON_SMOOTH)
1293 one = self.appearance.emsize/4.0
1294
1295 vv = numpy.zeros(shape=(len(samples),2), dtype='float32')
1296 vv[:,0] = numpy.arange(len(samples), dtype='float32')
1297 vv[:,0] *= dx
1298 vv[:,0] += x0
1299 vv[:,1] = samples
1300 OpenGL.GL.glVertexPointerf(vv)
1301 OpenGL.GL.glEnableClientState(OpenGL.GL.GL_VERTEX_ARRAY)
1302 OpenGL.GL.glDrawArrays(OpenGL.GL.GL_LINE_STRIP, 0, len(samples))
1303 if seg_n < (w/4):
1304 pvv = numpy.zeros(shape=(DOT_DIVISIONS,2), dtype='float32')
1305 angle = numpy.arange(DOT_DIVISIONS, dtype='float32') * (360.0 / DOT_DIVISIONS)
1306 pvv[:,0] = numpy.sin(angle)
1307 pvv[:,1] = numpy.cos(angle)
1308 pvv *= 1.5*one
1309 OpenGL.GL.glVertexPointerf(pvv)
1310 for i in xrange(len(samples)):
1311 OpenGL.GL.glPushMatrix()
1312 OpenGL.GL.glTranslatef(vv[i,0], vv[i,1], 0.0)
1313 OpenGL.GL.glDrawArrays(OpenGL.GL.GL_TRIANGLE_FAN, 0, DOT_DIVISIONS)
1314 OpenGL.GL.glPopMatrix()
1315 OpenGL.GL.glDisableClientState(OpenGL.GL.GL_VERTEX_ARRAY)
1316 OpenGL.GL.glDisable(OpenGL.GL.GL_LINE_SMOOTH)
1317 OpenGL.GL.glDisable(OpenGL.GL.GL_POLYGON_SMOOTH)
1318 - def draw_samples_raster_gl(self, gldrawable, glcontext, w, h, center, per_div, sig_color, lows, highs, gauss_los, gauss_his, pixels_from_start, vv):
1319 color_tup = self.appearance.color(sig_color)
1320 OpenGL.GL.glPushMatrix()
1321 OpenGL.GL.glTranslate(0.0, h/2.0, 0.0)
1322 OpenGL.GL.glScale(1.0, - self.pix_per_div[1] / per_div, 1.0)
1323 OpenGL.GL.glTranslate(0.0, - center, 0.0)
1324
1325 OpenGL.GL.glEnableClientState(OpenGL.GL.GL_VERTEX_ARRAY)
1326 OpenGL.GL.glVertexPointerf(vv)
1327 OpenGL.GL.glLineWidth(1.0)
1328
1329 cPartial = qubx.toolspace.SETALPHA(color_tup, .5*color_tup[3])
1330
1331 N = min(len(lows), (vv.shape[0] - 2*pixels_from_start)/2)
1332 vv[2*pixels_from_start:2*(pixels_from_start+N):2,1] = lows[:N]
1333 vv[2*pixels_from_start+1:2*(pixels_from_start+N):2,1] = highs[:N]
1334 OpenGL.GL.glColor4f(*cPartial)
1335 OpenGL.GL.glDrawArrays(OpenGL.GL.GL_LINES, 2*pixels_from_start, 2*N)
1336
1337 vv[2*pixels_from_start:2*(pixels_from_start+N):2,1] = gauss_los[:N]
1338 vv[2*pixels_from_start+1:2*(pixels_from_start+N):2,1] = gauss_his[:N]
1339 OpenGL.GL.glColor4f(*color_tup)
1340 OpenGL.GL.glDrawArrays(OpenGL.GL.GL_LINES, 2*pixels_from_start, 2*N)
1341 OpenGL.GL.glPopMatrix()
1342 - def draw_idl_gl(self, gldrawable, glcontext, w, h, center, per_div, sig, seg, offset, vv):
1343 t_center, t_per_div = [self.signals.get(0, x) for x in ('Center', 'per div')]
1344 t_left = t_center - self.divs[0] * t_per_div / 2
1345 ppd_x, ppd_y = self.pix_per_div
1346 ppsec = ppd_x / t_per_div
1347 seg_n = seg.l - seg.f + 1
1348 seg_dur = seg_n * seg.file.sampling
1349 seg_w = min(w, int(ceil(seg_dur * ppsec)))
1350 seg_index = self.file.segmentation.index_at(seg.f)
1351 lows, highs, class_bits = self.get_sampled_events(sig, seg_index, seg.f, seg.l, seg_w)
1352 OpenGL.GL.glPushMatrix()
1353 xoff = int(round(seg.file.sampling * offset * ppsec))
1354 OpenGL.GL.glTranslatef(xoff, 0, 0)
1355 self.draw_sampled_idl_gl(gldrawable, glcontext, seg_w, h, xoff, center, per_div, seg, lows, highs, vv)
1356 OpenGL.GL.glPopMatrix()
1357 - def draw_sampled_idl_gl(self, gldrawable, glcontext, w, h, xoff, center, per_div, seg, lows, highs, vv, color_inc=qubx.scope.COLOR_IDEALIZATION, color_exc=qubx.scope.COLOR_EXCLUDED):
1358 if not numpy.any(lows != UNSET_IDL):
1359 return
1360 dt = self.file.sampling
1361 halfwid = w / 2
1362 t_center, t_per_div = [self.signals.get(0, x) for x in ('Center', 'per div')]
1363 ppd_x, ppd_y = self.pix_per_div
1364 ppsec = ppd_x / t_per_div
1365
1366 OpenGL.GL.glPushMatrix()
1367 OpenGL.GL.glTranslate(0.0, h/2, 0.0)
1368 OpenGL.GL.glScale(1.0, - ppd_y / per_div, 1.0)
1369 OpenGL.GL.glTranslate(0.0, - center, 0.0)
1370 units_per_pix = per_div / ppd_y
1371
1372 OpenGL.GL.glEnableClientState(OpenGL.GL.GL_VERTEX_ARRAY)
1373 OpenGL.GL.glVertexPointerf(vv)
1374 OpenGL.GL.glLineWidth(self.appearance.line_width*self.appearance.emsize/8.0)
1375
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))
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
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
1472 if r is None:
1473 r = self.time.sel_right if (self.time.sel_left != self.time.sel_right) else self.time.right
1474 return self.file.get_segmentation(segf, segl, l, r, sig, latency, baseline_nodes=baseline_nodes)
1492
1493
1494 fields = property(lambda self: self.nb_get_headers(), doc="for use with Notebook_Table_Extensions")
1502 - def nb_draw(self, context, w, h):
1506
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)
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)
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
1639
1640
1641
1642
1643
1644
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
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
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
1687 - def __init__(self, i_signal, tiles_per_seg=3, tile_width=1024):
1688 self.__i_signal = i_signal
1689 self.__tiles_per_seg = tiles_per_seg
1690 self.__tile_width = tile_width
1691 self.__samp_per_pix = 1.0
1692 self.center = 1.0
1693 self.per_div = 1.0
1694 self.__last_segb = (-1, -1)
1695 self.__Nseg = 1
1696 self.serial = 0
1697 self.__baseline_nodes = True
1698 self.active_heap = []
1699 self.spare_list = []
1700 self.tile_map = {}
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))
1704 if self.__samp_per_pix == x: return
1705 self.__samp_per_pix = x
1706 self.clear()
1707 i_signal = property(lambda self: self.__i_signal, lambda self, x: self.set_i_signal(x))
1709 if self.__i_signal == x: return
1710 self.__i_signal = x
1711 self.clear()
1712 tiles_per_seg = property(lambda self: self.__tiles_per_seg, lambda self, x: self.set_tiles_per_seg(x))
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
1729 else:
1730 serial, tile = heapq.heappop(self.active_heap)
1731 del self.tile_map[(tile.i_seg, tile.i_left)]
1732
1733 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
1746 break
1748 tile.serial = self.serial
1749 self.serial += 1
1750 heapq.heappush(self.active_heap, (tile.serial, tile))
1751 return tile
1753
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
1771
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
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)))
1786 - def get_tile(self, view, i_seg, i_left, Nsigseg=1, baseline_nodes=True):
1787
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
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
1798
1799
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:
1807 tile = self.get_tile(view, i_seg, tile_left, Nsigseg, baseline_nodes)
1808
1809 tile_n = min(self.__tile_width - at, tile.actual_n - tile_i)
1810 if tile_n <= 0:
1811 break
1812
1813
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
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
1841 - def __init__(self, datas_table, menu_width):
1842 """
1843 @param datas_table: the L{qubx.data_types.QubDatas} table of open data files
1844 @param menu_width: width of the open-files menu, in widths of 'M'
1845 """
1846 QubDataView_Base.__init__(self, 'QubX.Data.view', True)
1847 self.__ref = Reffer()
1848 self.layNotebook.x = menu_width+5
1849 self.notebook['Pick'] = qubx.notebookGTK.NbSubTablePicker(self, "%s.notebook['Pick']"%self.global_name, self.nb_get_caption, NbSubTable='qubx.dataGTK.NbSubTrace')
1850 self.subNotebook.items.append(self.notebook['Pick'])
1851
1852 self.__serial_auto_scale = 0
1853
1854 self.signals.OnAfterInsert += self.__ref(self.__onAfterInsertSignal)
1855 self.signals.OnSet += self.__ref(self.__onSetSignal)
1856 self.__ignore_evt = False
1857 self.layNotebook.x = menu_width + 5
1858
1859 self.layDisplay = qubx.toolspace.Layer(menu_width+7.75, -3.5, 2.5, 2.5, qubx.toolspace.COLOR_CLEAR)
1860 self.controls.add_layer(self.layDisplay)
1861 self.subDisplay = SubLayer_DisplayMenu()
1862 self.subDisplay.tooltip = 'Display menu'
1863 self.layDisplay.add_sublayer(self.subDisplay)
1864
1865 self.time = TimeControlLayer(self.signals, menu_width+11)
1866 self.time.OnChangeShowing += self.__ref(self.__onChangeShowing)
1867 self.time.OnChangeSel += self.__ref(self.__onChangeSel)
1868 self.controls.add_layer(self.time)
1869
1870 self.layZoom = qubx.toolspace.Layer(x=-3.5, y=2, w=3, h=1.5, cBG=COLOR_ZOOM_BG)
1871 self.controls.add_layer(self.layZoom)
1872 self.subZoomOut = qubx.toolspace.SubLayer_SmoothZoom('-', zoom_out=True, x=0, y=0, w=1.5, h=1.5)
1873 self.subZoomOut.OnZoom += self.__ref(self.__onZoom)
1874 self.layZoom.add_sublayer(self.subZoomOut)
1875 self.subZoomIn = qubx.toolspace.SubLayer_SmoothZoom('+', x=1.5, y=0, w=1.5, h=1.5)
1876 self.subZoomIn.OnZoom += self.__ref(self.__onZoom)
1877 self.layZoom.add_sublayer(self.subZoomIn)
1878
1879 self.hires = QubDataView_Hi(self.signals, self.time)
1880
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
1899
1900 if line_count > self.line_count:
1901 self.line_count = line_count
1902 self.time.Ndiv = dx * line_count
1903 else:
1904 self.time.Ndiv = dx * line_count
1905 self.line_count = line_count
1906
1907 return dx, hi_y
1926
1934 if self.auto_scale:
1935 self.__serial_auto_scale += 1
1936 gobject.idle_add(self.__do_auto_scale, self.__serial_auto_scale)
1942
1980
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:
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()
2032 if field_name in ['Name', 'Units']:
2033 self.signals.set(i+1, field_name, val)
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'])
2058 - def __onSetSignal(self, i, field_name, val, prev, undoing=False):
2059 if field_name == 'Idl above':
2060 self.__ignore_evt = True
2061 self.sig_layers[i].chkIdlAbove.set_active(val)
2062 self.__ignore_evt = False
2064 if sender != self:
2065 self.time.Iseg = i
2092 per_div = self.signals[0, 'per div']
2093 self.signals[0, 'per div'] = per_div / factor
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)
2122
2125 - def measure(self, left, right, source, to_list=True, script_name=None, wait=True):
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):
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
2177
2179 return (max(l0, l1), min(r0, r1))
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
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
2321
2323 if i == 0:
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
2371
2376 per_div = self.signals[0, 'per div']
2377 self.signals[0, 'per div'] = per_div / factor
2379 w = self.divs[0]*self.signals[0, 'per div']
2380 self.signals[0, 'Center'] = max(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
2401 self.subExclude.OnHideTooltip(self.subExclude)
2402 self.tool_exclude.do_popup(x, y, e)
2404 self.subClone.OnHideTooltip(self.subClone)
2405 self.tool_clone.do_popup(x, y, e)
2407 if not self.file: return
2408 self.subMeasure.OnHideTooltip(self.subMeasure)
2409 self.tool_ruler.do_popup(x, y, e)
2411 self.subBaseline.OnHideTooltip(self.subBaseline)
2412 self.tool_baseline.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
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:
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)
2470 qubx.pyenv.env.OnScriptable('QubX.Data.view.measure_datasource(script_name="Idealized.py")')
2471 self.measure_datasource(script_name='Idealized.py', wait=False)
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)
2524 def on_report(s):
2525 print s
2526 def on_percent(p):
2527 self.robot.progress = p
2528 try:
2529 result = qubopt.amp.AMP(config, segments, on_report, on_percent, self.robot.main_hold)
2530 gobject.idle_add(on_result, segments, result, update_model, show_ideal)
2531
2532 except:
2533 traceback.print_exc()
2534 result = qubx.tree_native.NullNode()
2535 receiver(result)
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
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))
2599 if serial == self.__bw_hint_serial:
2600 self.__bw_hint = ""
2601
2608
2618
2620 traceback.print_exception(typ, val, trace)
2621 - def measure(self, left, right, source, to_list=True, script_name=None, wait=True):
2622
2623 """Initiates amp,std measurement of all signals, within time range."""
2624 if to_list:
2625 measured = self.file.lists.show_list('Measured')
2626 if source == DATASOURCE_FILE:
2627 measured.clear()
2628 self.__screen_measurements = None
2629 else:
2630 measured = qubx.table.SimpleTable('', auto_add_fields=True)
2631 self.__screen_measurements = measured
2632 segments = self.get_segmentation(left=left, right=right, source=source, signal=qubx.pyenv.env.globals['QubX'].DataSource.signal)
2633 if wait:
2634 return qubx.pyenv.call_async_wait(lambda rcv: self.measure_segments(segments, table=measured, row_offset=measured.size, script_name=script_name, receiver=rcv))
2635 else:
2636 self.measure_segments(segments, table=measured, row_offset=measured.size, script_name=script_name)
2637 - def measure_list(self, list, script_name=None, wait=True, receiver=lambda tbl, off, ct: None):
2638 """Returns (Table, row_offset, row_count), either directly if wait is True,x or via receiver function."""
2639 segments = self.file.get_segmentation_list(list.list_name, signal=qubx.pyenv.env.globals['QubX'].DataSource.signal)
2640 if not segments: return
2641 if wait:
2642 return qubx.pyenv.call_async_wait(lambda rcv: self.measure_segments(segments, table=list, row_offset=0, script_name=script_name, receiver=rcv))
2643 else:
2644 self.measure_segments(segments, table=list, row_offset=0, script_name=script_name, receiver=receiver)
2645 - def measure_segments(self, segs, table=None, row_offset=None, script_name=None, receiver=lambda tbl, off, ct: None):
2646 """Returns (Table, row_offset, row_count), either directly if wait is True, or via receiver function."""
2647 if self.robot.in_use or self.robot.acting:
2648 self.robot.interrupt()
2649 gobject.idle_add(self.measure_segments, segs, table, row_offset, script_name, receiver)
2650 elif segs:
2651 self.robot.in_use = True
2652 tb = self.file.segments if (table is None) else table
2653 ro = segs[0].index if (row_offset is None) else row_offset
2654 if self.tool_ruler.load_script(script_name):
2655 trouble = False
2656 try:
2657 segs, tb, ro = qubx.pyenv.env.globals['before_measure'](segs, tb, ro)
2658 except:
2659 traceback.print_exc()
2660 trouble = True
2661 if segs:
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]
2694 sigsegs = [get_segmentation_copy(source_segs, signal=s) for s in xrange(segs[0].file.signals.size)]
2695 segsigs = [ [sigseg[i] for sigseg in sigsegs] for i in xrange(len(sigsegs[0])) ]
2696 view = [v for v in QubX.Data.views if v.file == segs[0].file][0]
2697 signames = [view.signals[i, 'Name'] for i in xrange(1, view.signals.size)]
2698 def get_samples(seg, first_offset=0, last_offset=None):
2699 with self.robot.main_hold:
2700 samples = seg.get_samples(first_offset, last_offset).samples
2701 if seg.signal == source_index:
2702 if baseline_type == RULER_BASELINE_CONST:
2703 samples -= baseline_const
2704 elif baseline_type == RULER_BASELINE_IN_SEG:
2705 samples -= seg_bl[seg.index]
2706 elif baseline_type == RULER_BASELINE_IN_SEL:
2707 bseg = seg.file.get_segmentation_indexed(seg.f + left_ix, seg.f + right_ix, signal=seg.signal)[0]
2708 last_baseline[0] = bseg.get_samples().samples.mean()
2709 samples -= last_baseline[0]
2710 return samples
2711 def get_idealization(seg, first_offset=0, last_offset=None, **kw):
2712 if not ('get_fragments' in kw):
2713 kw['get_fragments'] = True
2714 kw['get_durations'] = True
2715 kw['get_amps'] = True
2716 kw['get_stds'] = False
2717 with self.robot.main_hold:
2718 firsts, lasts, classes, durations, amps = seg.get_idealization(first_offset=first_offset, last_offset=last_offset, **kw)
2719 return firsts, lasts, classes, durations, amps
2720 rows = []
2721 for seg_ix, sigs in enumerate(segsigs):
2722 try:
2723 inclusion_mask = numpy.zeros(shape=(sigs[0].n,), dtype='int8')
2724 i = 0
2725 for chunk in sigs[0].chunks:
2726 if chunk.included:
2727 inclusion_mask[i:i+chunk.n] = 1
2728 i += chunk.n
2729 inclusion_mask = inclusion_mask.astype('bool')
2730 signals = [Anon(name=signames[sig_ix], get_samples=bind_with_args(get_samples, seg),
2731 get_idealization=bind_with_args(get_idealization, seg),
2732 hidden=self.signals[sig_ix+1,'Center'] == SIGNAL_CENTER_HIDDEN)
2733 for sig_ix, seg in enumerate(sigs)]
2734 seg = sigs[source_index]
2735 measured = {'From' : seg.f,
2736 'To' : seg.l,
2737 'Start' : seg.start,
2738 'Duration': seg.sampling * seg.n * 1e3}
2739
2740 qubx.pyenv.env.globals['measure_selection'](seg, signals, signals[source_index], inclusion_mask, measured)
2741 except:
2742 traceback.print_exc()
2743 trouble = True
2744 if baseline_type == RULER_BASELINE_CONST:
2745 measured['%s Baseline'%signals[source_index].name] = baseline_const
2746 elif baseline_type == RULER_BASELINE_IN_SEG:
2747 measured['%s Baseline'%signals[source_index].name] = seg_bl[sigs[0].index]
2748 elif baseline_type == RULER_BASELINE_IN_SEL:
2749 measured['%s Baseline'%signals[source_index].name] = last_baseline[0]
2750 rows.append(measured)
2751 self.robot.progress = seg_ix * 100.0 / len(segsigs)
2752 gobject.idle_add(self.__after_measure, segs, table, rows, row_offset, trouble, receiver)
2753 - def __after_measure(self, segs, table, rows, row_offset, trouble, receiver):
2754 for r, row in enumerate(rows):
2755 if (r + row_offset) < table.size:
2756 for k in sorted(row.keys()):
2757 if not (k in ('Index', 'From', 'To')):
2758 table[r+row_offset, k] = row[k]
2759 else:
2760 table.append(row)
2761 try:
2762 qubx.pyenv.env.globals['after_measure'](segs, table, row_offset)
2763 except:
2764 traceback.print_exc()
2765 trouble = True
2766 try:
2767 receiver(table, row_offset, len(rows))
2768 except:
2769 traceback.print_exc()
2770 if trouble:
2771 mdlg = gtk.MessageDialog(None, buttons=gtk.BUTTONS_OK, flags=gtk.DIALOG_MODAL,
2772 message_format= "Error in measurement script. See Admin:Scripts for details.")
2773 mdlg.run()
2774 mdlg.destroy()
2775 if table == self.__screen_measurements:
2776 self.update_hint()
2777 else:
2778 view = self.QubX.Tables.find_view(table.label)
2779 if view and (view.table == table):
2780 qubx.notebook.Notebook.send(view.nbTable, auto_id='Measure.Segments')
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
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
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
2852
2853
2854
2855
2856
2857
2858 -def before_measure(segments, table, row_offset):
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
2886
2887
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
2926
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
3093
3146
3175
3285
3329
3598
3599
3600 CLONE_MODE_SOURCE, CLONE_MODE_DEST, CLONE_MODE_RESTORE = range(3)
3660
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
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
4109
4110 Tools.register('ideal', QubData_JoinTool.tool_label, tool_class=QubData_JoinTool)
4111
4112
4113
4114 -class Layer_Lists(qubx.toolspace.Layer):
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
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
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)
4217
4223 Tools.register('list', 'Extract data from selections...', ExtractList)
4231 Tools.register('list', 'Exclude selections', ExcludeList)
4239 Tools.register('list', 'Include selections', IncludeList)
4245 Tools.register('list', 'Baum-Welch (AMP)', BaumWelchList)
4246
4247
4248 TOO_MANY_LIST_ITEMS_TO_DRAW = 2048
4269 if not (self.__list is None):
4270 for event in (self.__list.OnInsert, self.__list.OnRemoved, self.__list.OnSet, self.__list.OnChangeListName, self.__list.OnSelect, self.__list.OnChecked):
4271 event -= self.__ref(self.__onEdit)
4272 self.__list = x
4273 if not (self.__list is None):
4274 for event in (self.__list.OnInsert, self.__list.OnRemoved, self.__list.OnSet, self.__list.OnChangeListName, self.__list.OnSelect, self.__list.OnChecked):
4275 event += self.__ref(self.__onEdit)
4276 self.invalidate()
4277 list = property(lambda self: self.__list, lambda self, x: self.set_list(x))
4278 - def draw(self, context, w, h, appearance):
4279 qubx.toolspace.SubLayer.draw(self, context, w, h, appearance)
4280 if self.__pressed and (self.__press_ix < 0):
4281 press_x, press_y = self.__press_coord
4282 x, y = self.__at_coord
4283 left, right = sorted([press_x, x])
4284 context.set_source_rgba(*appearance.color(COLOR_LIST))
4285 context.rectangle(left, 0, right-left, h)
4286 context.fill()
4287 space = self.layer.space
4288 lxpp, lypp = space._layer_scale
4289 center, per_div = space.signals.get(0, 'Center'), space.signals.get(0, 'per div')
4290 self.x2t = lambda x: space.x2u(x/lxpp, 0, center, per_div)
4291 self.t2x = lambda t: lxpp*space.u2x(t, center, per_div)
4292 xx, yy = [], []
4293 y = self.__dotrad = h/2
4294 self.dotrow = []
4295 if not (self.__list is None):
4296 context.save()
4297 context.set_source_rgba(*appearance.color(COLOR_LIST_NAME))
4298 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents()
4299 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(self.__list.list_name)
4300 vtotal = fascent + fdescent
4301 by = (self.h - vtotal)/2 + fascent
4302 bx = (self.w - width) - xbearing - 5*appearance.emsize
4303 context.move_to(bx, by)
4304 context.show_text(self.__list.list_name)
4305
4306 context.set_font_size(int(round(.5*h)))
4307 fascent, fdescent, fheight, fxadvance, fyadvance = context.font_extents()
4308 vtotal = fascent + fdescent
4309 segs = space.get_segmentation_screen()
4310 if segs:
4311 seg = segs[0]
4312 sels = self.__list.selections_in(seg.f, seg.l, trimmed=True)
4313 if len(sels) < TOO_MANY_LIST_ITEMS_TO_DRAW:
4314 for From, To, Label, Index in sels:
4315 if Index == space.list_sel:
4316 context.set_source_rgba(*appearance.color(COLOR_LIST_SEL))
4317 elif self.__list.checked[Index]:
4318 context.set_source_rgba(*appearance.color(COLOR_LIST_CHECKED))
4319 else:
4320 continue
4321 x0 = self.t2x(space.file.sampling*(From - seg.offset))
4322 x1 = self.t2x(space.file.sampling*(To - seg.offset))
4323 context.rectangle(x0, 0, x1-x0, self.h)
4324 context.fill()
4325 for From, To, Label, Index in self.__list.selections_in(seg.f, seg.l, trimmed=True):
4326 x = self.t2x(space.file.sampling*(From - seg.offset))
4327 xx.append(x)
4328 yy.append(y)
4329 context.set_source_rgba(*appearance.color(COLOR_LIST_BEAD))
4330 context.arc(x, y, .72*y, 0, 2*pi)
4331 context.fill()
4332 bead_lbl = str(Index)
4333 context.set_source_rgba(*appearance.color(COLOR_LIST_BEAD_LABEL))
4334 xbearing, ybearing, width, height, xadvance, yadvance = context.text_extents(bead_lbl)
4335 by = (self.h - vtotal)/2 + fascent
4336 bx = (x - width/2) - xbearing
4337 context.move_to(bx, by)
4338 context.show_text(bead_lbl)
4339 self.dotrow.append(Index)
4340 context.restore()
4341 self.dotmap.reset(xx, yy)
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
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)
4419
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
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 """
4499 source = property(lambda self: self.__source, lambda self, x: self.set_source(x))
4509 signal = property(lambda self: self.__signal, lambda self, x: self.set_signal(x))
4538 data = property(lambda self: self.__data, lambda self, x: self.set_data(x))
4569 self.OnChangeSamples()
4571 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]
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
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
4671
4672
4673 -def NbSubTrace(name='SubTable', fields=[]):
4683 make_nb.name = name
4684 return make_nb
4685
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 """)
4756 self.hide()
4757 return True
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)
4819 - def run(self, view, model):
4820 QubX = qubx.global_namespace.QubX
4821 xser = view.signals[1+QubX.DataSource.signal, 'Name']
4822 i, is_log = DoseResponse_FindStim(view, model)
4823 if i is None:
4824 qubx.GTK.ShowMessage("""No stimulus detected.
4825
4826 To pick a stimulus signal, first find its name in the Data panel, e.g. "Voltage." Then right-click (or long-press) a rate in the model, choose "Ligand-sensitive" or "Voltage-sensitive," and enter the signal name.""")
4827 return gtk.RESPONSE_REJECT
4828 yser = view.signals[i+1, 'Name']
4829 if is_log:
4830 yser = 'log10(%s)' % yser
4831 self.lblVs.set_text('%s v. %s' % (yser, xser))
4832 return gtk.Dialog.run(self)
4833
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):
4931 cancel()
4932 self.__stop_flag = True
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
4957 for seg_stim, seg in izip(reversed(segments_stim), reversed(segments)):
4958 for chunk_dose, chunk_response in izip(reversed(seg_stim.chunks), reversed(seg.chunks)):
4959 if chunk_dose.included:
4960 dr.add_data(chunk_dose.get_samples().samples, chunk_response.get_samples().samples)
4961 if self.measure == DOSERESPONSE_MEASURE_PEAK:
4962 dose, response = dr.result(DOSERESPONSE_MAX_RESULTS)
4963 else:
4964 dose, response, resp_std = dr.result(DOSERESPONSE_MAX_RESULTS)
4965 if self.normalize:
4966 if sign > 0:
4967 response -= numpy.min(response)
4968 max_resp = numpy.max(response)
4969 response /= max_resp
4970 else:
4971 response -= numpy.max(response)
4972 max_resp = numpy.min(response)
4973 response /= max_resp
4974 if self.measure != DOSERESPONSE_MEASURE_PEAK:
4975 resp_std /= max_resp
4976
4977 gobject.idle_add(self.finish, stim, dose, is_log, cur, response)
4978 - def finish(self, stim, dose, is_log, cur, response):
4979 QubX = qubx.global_namespace.QubX
4980 tbl = QubX.Tables.find_table(self.output_name) or QubX.Tables.new_table(self.output_name)
4981 tbl.clear()
4982 QubX.Tables.show_table(tbl)
4983 if not (dose is None):
4984 nm_dose = self.view.signals[1+stim, "Name"]
4985 nm_response = self.view.signals[1+cur, "Name"]
4986 for d,r in izip(dose, response):
4987 tbl.append({nm_dose:d, nm_response:r})
4988
4989 plot = QubX.Figures.Charts.add_two_plot(self.output_name, nm_dose, nm_response, is_log, False)
4990 QubX.show_charts()
4991
4992
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