2 # Copyright 2008 Free Software Foundation, Inc.
4 # This file is part of GNU Radio
6 # GNU Radio 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, or (at your option)
11 # GNU Radio 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.
16 # You should have received a copy of the GNU General Public License
17 # along with GNU Radio; see the file COPYING. If not, write to
18 # the Free Software Foundation, Inc., 51 Franklin Street,
19 # Boston, MA 02110-1301, USA.
22 ##################################################
24 ##################################################
31 from constants import *
32 from gnuradio import gr #for gr.prefs, trigger modes
35 ##################################################
37 ##################################################
38 DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'scope_rate', 30)
39 DEFAULT_WIN_SIZE = (600, 300)
45 ('Freerun', gr.gr_TRIG_MODE_FREE),
46 ('Auto', gr.gr_TRIG_MODE_AUTO),
47 ('Normal', gr.gr_TRIG_MODE_NORM),
50 ('Pos +', gr.gr_TRIG_SLOPE_POS),
51 ('Neg -', gr.gr_TRIG_SLOPE_NEG),
53 CHANNEL_COLOR_SPECS = (
62 TRIGGER_COLOR_SPEC = (1.0, 0.4, 0.0)
63 AUTORANGE_UPDATE_RATE = 0.5 #sec
71 DEFAULT_MARKER_TYPE = None
73 ##################################################
74 # Scope window control panel
75 ##################################################
76 class control_panel(wx.Panel):
78 A control panel with wx widgits to control the plotter and scope block.
80 def __init__(self, parent):
82 Create a new control panel.
83 @param parent the wx parent window
87 wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER)
88 parent[SHOW_CONTROL_PANEL_KEY] = True
89 parent.subscribe(SHOW_CONTROL_PANEL_KEY, self.Show)
90 control_box = wx.BoxSizer(wx.VERTICAL)
91 ##################################################
93 ##################################################
94 control_box.AddStretchSpacer()
95 axes_options_box = forms.static_box_sizer(
96 parent=self, sizer=control_box, label='Axes Options',
97 bold=True, orient=wx.VERTICAL,
99 ##################################################
101 ##################################################
102 scope_mode_box = wx.BoxSizer(wx.VERTICAL)
103 axes_options_box.Add(scope_mode_box, 0, wx.EXPAND)
105 forms.incr_decr_buttons(
106 parent=self, sizer=scope_mode_box, label='Secs/Div',
107 on_incr=self._on_incr_t_divs, on_decr=self._on_decr_t_divs,
110 y_buttons_scope = forms.incr_decr_buttons(
111 parent=self, sizer=scope_mode_box, label='Counts/Div',
112 on_incr=self._on_incr_y_divs, on_decr=self._on_decr_y_divs,
115 y_off_buttons_scope = forms.incr_decr_buttons(
116 parent=self, sizer=scope_mode_box, label='Y Offset',
117 on_incr=self._on_incr_y_off, on_decr=self._on_decr_y_off,
120 scope_mode_box.AddSpacer(5)
122 parent=self, sizer=scope_mode_box,
123 ps=parent, key=T_FRAC_OFF_KEY, label='T Offset',
124 minimum=0, maximum=1, num_steps=1000,
126 scope_mode_box.AddSpacer(5)
127 ##################################################
129 ##################################################
130 xy_mode_box = wx.BoxSizer(wx.VERTICAL)
131 axes_options_box.Add(xy_mode_box, 0, wx.EXPAND)
133 x_buttons = forms.incr_decr_buttons(
134 parent=self, sizer=xy_mode_box, label='X/Div',
135 on_incr=self._on_incr_x_divs, on_decr=self._on_decr_x_divs,
138 y_buttons = forms.incr_decr_buttons(
139 parent=self, sizer=xy_mode_box, label='Y/Div',
140 on_incr=self._on_incr_y_divs, on_decr=self._on_decr_y_divs,
143 x_off_buttons = forms.incr_decr_buttons(
144 parent=self, sizer=xy_mode_box, label='X Off',
145 on_incr=self._on_incr_x_off, on_decr=self._on_decr_x_off,
148 y_off_buttons = forms.incr_decr_buttons(
149 parent=self, sizer=xy_mode_box, label='Y Off',
150 on_incr=self._on_incr_y_off, on_decr=self._on_decr_y_off,
152 for widget in (y_buttons_scope, y_off_buttons_scope, x_buttons, y_buttons, x_off_buttons, y_off_buttons):
153 parent.subscribe(AUTORANGE_KEY, widget.Disable)
154 widget.Disable(parent[AUTORANGE_KEY])
155 xy_mode_box.ShowItems(False)
158 parent=self, sizer=axes_options_box, label='Autorange',
159 ps=parent, key=AUTORANGE_KEY,
161 ##################################################
163 ##################################################
164 TRIGGER_PAGE_INDEX = parent.num_inputs
165 XY_PAGE_INDEX = parent.num_inputs+1
166 control_box.AddStretchSpacer()
167 chan_options_box = forms.static_box_sizer(
168 parent=self, sizer=control_box, label='Channel Options',
169 bold=True, orient=wx.VERTICAL,
171 options_notebook = wx.Notebook(self)
172 options_notebook_args = list()
173 CHANNELS = [('Ch %d'%(i+1), i) for i in range(parent.num_inputs)]
174 ##################################################
176 ##################################################
177 for i in range(parent.num_inputs):
178 channel_menu_panel = wx.Panel(options_notebook)
179 options_notebook_args.append((channel_menu_panel, i, 'Ch%d'%(i+1)))
180 channel_menu_box = wx.BoxSizer(wx.VERTICAL)
181 channel_menu_panel.SetSizer(channel_menu_box)
183 channel_menu_box.AddStretchSpacer()
185 parent=channel_menu_panel, sizer=channel_menu_box,
186 ps=parent, key=common.index_key(AC_COUPLE_KEY, i),
187 choices=map(lambda x: x[1], COUPLING_MODES),
188 labels=map(lambda x: x[0], COUPLING_MODES),
189 label='Coupling', width=WIDTH,
192 channel_menu_box.AddStretchSpacer()
194 parent=channel_menu_panel, sizer=channel_menu_box,
195 ps=parent, key=common.index_key(MARKER_KEY, i),
196 choices=map(lambda x: x[1], MARKER_TYPES),
197 labels=map(lambda x: x[0], MARKER_TYPES),
198 label='Marker', width=WIDTH,
200 channel_menu_box.AddStretchSpacer()
201 ##################################################
203 ##################################################
204 trigger_menu_panel = wx.Panel(options_notebook)
205 options_notebook_args.append((trigger_menu_panel, TRIGGER_PAGE_INDEX, 'Trig'))
206 trigger_menu_box = wx.BoxSizer(wx.VERTICAL)
207 trigger_menu_panel.SetSizer(trigger_menu_box)
210 parent=trigger_menu_panel, sizer=trigger_menu_box,
211 ps=parent, key=TRIGGER_MODE_KEY,
212 choices=map(lambda x: x[1], TRIGGER_MODES),
213 labels=map(lambda x: x[0], TRIGGER_MODES),
214 label='Mode', width=WIDTH,
217 trigger_slope_chooser = forms.drop_down(
218 parent=trigger_menu_panel, sizer=trigger_menu_box,
219 ps=parent, key=TRIGGER_SLOPE_KEY,
220 choices=map(lambda x: x[1], TRIGGER_SLOPES),
221 labels=map(lambda x: x[0], TRIGGER_SLOPES),
222 label='Slope', width=WIDTH,
225 trigger_channel_chooser = forms.drop_down(
226 parent=trigger_menu_panel, sizer=trigger_menu_box,
227 ps=parent, key=TRIGGER_CHANNEL_KEY,
228 choices=map(lambda x: x[1], CHANNELS),
229 labels=map(lambda x: x[0], CHANNELS),
230 label='Channel', width=WIDTH,
233 hbox = wx.BoxSizer(wx.HORIZONTAL)
234 trigger_menu_box.Add(hbox, 0, wx.EXPAND)
235 hbox.Add(wx.StaticText(trigger_menu_panel, label='Level:'), 1, wx.ALIGN_CENTER_VERTICAL)
236 trigger_level_button = forms.single_button(
237 parent=trigger_menu_panel, sizer=hbox, label='50%',
238 callback=parent.set_auto_trigger_level, style=wx.BU_EXACTFIT,
240 hbox.AddSpacer(WIDTH-60)
241 trigger_level_buttons = forms.incr_decr_buttons(
242 parent=trigger_menu_panel, sizer=hbox,
243 on_incr=self._on_incr_trigger_level, on_decr=self._on_decr_trigger_level,
245 def disable_all(trigger_mode):
246 for widget in (trigger_slope_chooser, trigger_channel_chooser, trigger_level_buttons, trigger_level_button):
247 widget.Disable(trigger_mode == gr.gr_TRIG_MODE_FREE)
248 parent.subscribe(TRIGGER_MODE_KEY, disable_all)
249 disable_all(parent[TRIGGER_MODE_KEY])
250 ##################################################
252 ##################################################
253 if parent.num_inputs > 1:
254 xy_menu_panel = wx.Panel(options_notebook)
255 options_notebook_args.append((xy_menu_panel, XY_PAGE_INDEX, 'XY'))
256 xy_menu_box = wx.BoxSizer(wx.VERTICAL)
257 xy_menu_panel.SetSizer(xy_menu_box)
258 #x and y channel choosers
259 xy_menu_box.AddStretchSpacer()
261 parent=xy_menu_panel, sizer=xy_menu_box,
262 ps=parent, key=X_CHANNEL_KEY,
263 choices=map(lambda x: x[1], CHANNELS),
264 labels=map(lambda x: x[0], CHANNELS),
265 label='Channel X', width=WIDTH,
267 xy_menu_box.AddStretchSpacer()
269 parent=xy_menu_panel, sizer=xy_menu_box,
270 ps=parent, key=Y_CHANNEL_KEY,
271 choices=map(lambda x: x[1], CHANNELS),
272 labels=map(lambda x: x[0], CHANNELS),
273 label='Channel Y', width=WIDTH,
276 xy_menu_box.AddStretchSpacer()
278 parent=xy_menu_panel, sizer=xy_menu_box,
279 ps=parent, key=XY_MARKER_KEY,
280 choices=map(lambda x: x[1], MARKER_TYPES),
281 labels=map(lambda x: x[0], MARKER_TYPES),
282 label='Marker', width=WIDTH,
284 xy_menu_box.AddStretchSpacer()
285 ##################################################
286 # Setup Options Notebook
287 ##################################################
289 parent=self, sizer=chan_options_box,
290 notebook=options_notebook,
291 ps=parent, key=CHANNEL_OPTIONS_KEY,
292 pages=map(lambda x: x[0], options_notebook_args),
293 choices=map(lambda x: x[1], options_notebook_args),
294 labels=map(lambda x: x[2], options_notebook_args),
296 #gui handling for channel options changing
297 def options_notebook_changed(chan_opt):
299 parent[TRIGGER_SHOW_KEY] = chan_opt == TRIGGER_PAGE_INDEX
300 parent[XY_MODE_KEY] = chan_opt == XY_PAGE_INDEX
301 except wx.PyDeadObjectError: pass
302 parent.subscribe(CHANNEL_OPTIONS_KEY, options_notebook_changed)
303 #gui handling for xy mode changing
304 def xy_mode_changed(mode):
305 #ensure xy tab is selected
306 if mode and parent[CHANNEL_OPTIONS_KEY] != XY_PAGE_INDEX:
307 parent[CHANNEL_OPTIONS_KEY] = XY_PAGE_INDEX
308 #ensure xy tab is not selected
309 elif not mode and parent[CHANNEL_OPTIONS_KEY] == XY_PAGE_INDEX:
310 parent[CHANNEL_OPTIONS_KEY] = 0
311 #show/hide control buttons
312 scope_mode_box.ShowItems(not mode)
313 xy_mode_box.ShowItems(mode)
315 parent.subscribe(XY_MODE_KEY, xy_mode_changed)
316 xy_mode_changed(parent[XY_MODE_KEY])
317 ##################################################
319 ##################################################
321 control_box.AddStretchSpacer()
323 sizer=control_box, parent=self,
324 true_label='Stop', false_label='Run',
325 ps=parent, key=RUNNING_KEY,
328 self.SetSizerAndFit(control_box)
330 def on_mouse_wheel(event):
331 if not parent[XY_MODE_KEY]:
332 if event.GetWheelRotation() < 0: self._on_incr_t_divs(event)
333 else: self._on_decr_t_divs(event)
334 parent.plotter.Bind(wx.EVT_MOUSEWHEEL, on_mouse_wheel)
336 ##################################################
338 ##################################################
340 def _on_incr_trigger_level(self, event):
341 self.parent[TRIGGER_LEVEL_KEY] += self.parent[Y_PER_DIV_KEY]/3.
342 def _on_decr_trigger_level(self, event):
343 self.parent[TRIGGER_LEVEL_KEY] -= self.parent[Y_PER_DIV_KEY]/3.
345 def _on_incr_t_divs(self, event):
346 self.parent[T_PER_DIV_KEY] = common.get_clean_incr(self.parent[T_PER_DIV_KEY])
347 def _on_decr_t_divs(self, event):
348 self.parent[T_PER_DIV_KEY] = common.get_clean_decr(self.parent[T_PER_DIV_KEY])
349 def _on_incr_x_divs(self, event):
350 self.parent[X_PER_DIV_KEY] = common.get_clean_incr(self.parent[X_PER_DIV_KEY])
351 def _on_decr_x_divs(self, event):
352 self.parent[X_PER_DIV_KEY] = common.get_clean_decr(self.parent[X_PER_DIV_KEY])
353 def _on_incr_y_divs(self, event):
354 self.parent[Y_PER_DIV_KEY] = common.get_clean_incr(self.parent[Y_PER_DIV_KEY])
355 def _on_decr_y_divs(self, event):
356 self.parent[Y_PER_DIV_KEY] = common.get_clean_decr(self.parent[Y_PER_DIV_KEY])
358 def _on_incr_x_off(self, event):
359 self.parent[X_OFF_KEY] = self.parent[X_OFF_KEY] + self.parent[X_PER_DIV_KEY]
360 def _on_decr_x_off(self, event):
361 self.parent[X_OFF_KEY] = self.parent[X_OFF_KEY] - self.parent[X_PER_DIV_KEY]
362 def _on_incr_y_off(self, event):
363 self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] + self.parent[Y_PER_DIV_KEY]
364 def _on_decr_y_off(self, event):
365 self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY]
367 ##################################################
368 # Scope window with plotter and control panel
369 ##################################################
370 class scope_window(wx.Panel, pubsub.pubsub):
392 pubsub.pubsub.__init__(self)
394 assert num_inputs <= len(CHANNEL_COLOR_SPECS)
396 self.sampleses = None
397 self.num_inputs = num_inputs
398 autorange = not v_scale
399 self.autorange_ts = 0
400 v_scale = v_scale or 1
401 self.frame_rate_ts = 0
403 self.proxy(MSG_KEY, controller, msg_key)
404 self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key)
405 self.proxy(TRIGGER_LEVEL_KEY, controller, trigger_level_key)
406 self.proxy(TRIGGER_MODE_KEY, controller, trigger_mode_key)
407 self.proxy(TRIGGER_SLOPE_KEY, controller, trigger_slope_key)
408 self.proxy(TRIGGER_CHANNEL_KEY, controller, trigger_channel_key)
409 self.proxy(DECIMATION_KEY, controller, decimation_key)
411 self[RUNNING_KEY] = True
412 self[XY_MARKER_KEY] = 2.0
413 self[CHANNEL_OPTIONS_KEY] = 0
414 self[XY_MODE_KEY] = xy_mode
415 self[X_CHANNEL_KEY] = 0
416 self[Y_CHANNEL_KEY] = self.num_inputs-1
417 self[AUTORANGE_KEY] = autorange
418 self[T_PER_DIV_KEY] = t_scale
419 self[X_PER_DIV_KEY] = v_scale
420 self[Y_PER_DIV_KEY] = v_scale
422 self[X_OFF_KEY] = v_offset
423 self[Y_OFF_KEY] = v_offset
427 self[FRAME_RATE_KEY] = frame_rate
428 self[TRIGGER_LEVEL_KEY] = 0
429 self[TRIGGER_CHANNEL_KEY] = 0
430 self[TRIGGER_MODE_KEY] = gr.gr_TRIG_MODE_AUTO
431 self[TRIGGER_SLOPE_KEY] = gr.gr_TRIG_SLOPE_POS
432 self[T_FRAC_OFF_KEY] = 0.5
433 for i in range(num_inputs):
434 self.proxy(common.index_key(AC_COUPLE_KEY, i), controller, common.index_key(ac_couple_key, i))
436 wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER)
437 self.plotter = plotter.channel_plotter(self)
438 self.plotter.SetSize(wx.Size(*size))
439 self.plotter.set_title(title)
440 self.plotter.enable_legend(True)
441 self.plotter.enable_point_label(True)
442 self.plotter.enable_grid_lines(True)
443 #setup the box with plot and controls
444 self.control_panel = control_panel(self)
445 main_box = wx.BoxSizer(wx.HORIZONTAL)
446 main_box.Add(self.plotter, 1, wx.EXPAND)
447 main_box.Add(self.control_panel, 0, wx.EXPAND)
448 self.SetSizerAndFit(main_box)
449 #register events for message
450 self.subscribe(MSG_KEY, self.handle_msg)
451 #register events for grid
452 for key in [common.index_key(MARKER_KEY, i) for i in range(self.num_inputs)] + [
453 TRIGGER_LEVEL_KEY, TRIGGER_MODE_KEY,
454 T_PER_DIV_KEY, X_PER_DIV_KEY, Y_PER_DIV_KEY,
455 T_OFF_KEY, X_OFF_KEY, Y_OFF_KEY,
456 T_DIVS_KEY, X_DIVS_KEY, Y_DIVS_KEY,
457 XY_MODE_KEY, AUTORANGE_KEY, T_FRAC_OFF_KEY,
458 TRIGGER_SHOW_KEY, XY_MARKER_KEY, X_CHANNEL_KEY, Y_CHANNEL_KEY,
459 ]: self.subscribe(key, self.update_grid)
463 def handle_msg(self, msg):
465 Handle the message from the scope sink message queue.
466 Plot the list of arrays of samples onto the grid.
467 Each samples array gets its own channel.
468 @param msg the time domain data as a character array
470 if not self[RUNNING_KEY]: return
472 if time.time() - self.frame_rate_ts < 1.0/self[FRAME_RATE_KEY]: return
473 #convert to floating point numbers
474 samples = numpy.fromstring(msg, numpy.float32)
475 #extract the trigger offset
476 self.trigger_offset = samples[-1]
477 samples = samples[:-1]
478 samps_per_ch = len(samples)/self.num_inputs
479 self.sampleses = [samples[samps_per_ch*i:samps_per_ch*(i+1)] for i in range(self.num_inputs)]
481 self.handle_samples()
482 self.frame_rate_ts = time.time()
484 def set_auto_trigger_level(self, *args):
486 Use the current trigger channel and samples to calculate the 50% level.
488 if not self.sampleses: return
489 samples = self.sampleses[self[TRIGGER_CHANNEL_KEY]]
490 self[TRIGGER_LEVEL_KEY] = (numpy.max(samples)+numpy.min(samples))/2
492 def handle_samples(self):
494 Handle the cached samples from the scope input.
495 Perform ac coupling, triggering, and auto ranging.
497 if not self.sampleses: return
498 sampleses = self.sampleses
499 if self[XY_MODE_KEY]:
500 self[DECIMATION_KEY] = 1
501 x_samples = sampleses[self[X_CHANNEL_KEY]]
502 y_samples = sampleses[self[Y_CHANNEL_KEY]]
504 if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
505 x_min, x_max = common.get_min_max(x_samples)
506 y_min, y_max = common.get_min_max(y_samples)
507 #adjust the x per div
508 x_per_div = common.get_clean_num((x_max-x_min)/self[X_DIVS_KEY])
509 if x_per_div != self[X_PER_DIV_KEY]: self[X_PER_DIV_KEY] = x_per_div; return
511 x_off = x_per_div*round((x_max+x_min)/2/x_per_div)
512 if x_off != self[X_OFF_KEY]: self[X_OFF_KEY] = x_off; return
513 #adjust the y per div
514 y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
515 if y_per_div != self[Y_PER_DIV_KEY]: self[Y_PER_DIV_KEY] = y_per_div; return
517 y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
518 if y_off != self[Y_OFF_KEY]: self[Y_OFF_KEY] = y_off; return
519 self.autorange_ts = time.time()
521 self.plotter.set_waveform(
523 samples=(x_samples, y_samples),
524 color_spec=CHANNEL_COLOR_SPECS[0],
525 marker=self[XY_MARKER_KEY],
527 #turn off each waveform
528 for i, samples in enumerate(sampleses):
529 self.plotter.clear_waveform(channel='Ch%d'%(i+1))
532 if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
533 bounds = [common.get_min_max(samples) for samples in sampleses]
534 y_min = numpy.min([bound[0] for bound in bounds])
535 y_max = numpy.max([bound[1] for bound in bounds])
536 #adjust the y per div
537 y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
538 if y_per_div != self[Y_PER_DIV_KEY]: self[Y_PER_DIV_KEY] = y_per_div; return
540 y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
541 if y_off != self[Y_OFF_KEY]: self[Y_OFF_KEY] = y_off; return
542 self.autorange_ts = time.time()
543 #number of samples to scale to the screen
544 actual_rate = self.get_actual_rate()
545 time_span = self[T_PER_DIV_KEY]*self[T_DIVS_KEY]
546 num_samps = int(round(time_span*actual_rate))
547 #handle the time offset
548 t_off = self[T_FRAC_OFF_KEY]*(len(sampleses[0])/actual_rate - time_span)
549 if t_off != self[T_OFF_KEY]: self[T_OFF_KEY] = t_off; return
550 samps_off = int(round(actual_rate*self[T_OFF_KEY]))
551 #adjust the decim so that we use about half the samps
552 self[DECIMATION_KEY] = int(round(
553 time_span*self[SAMPLE_RATE_KEY]/(0.5*len(sampleses[0]))
556 #num samps too small, auto increment the time
557 if num_samps < 2: self[T_PER_DIV_KEY] = common.get_clean_incr(self[T_PER_DIV_KEY])
558 #num samps in bounds, plot each waveform
559 elif num_samps <= len(sampleses[0]):
560 for i, samples in enumerate(sampleses):
562 self.plotter.set_waveform(
563 channel='Ch%d'%(i+1),
564 samples=samples[samps_off:num_samps+samps_off],
565 color_spec=CHANNEL_COLOR_SPECS[i],
566 marker=self[common.index_key(MARKER_KEY, i)],
567 trig_off=self.trigger_offset,
570 self.plotter.clear_waveform(channel='XY')
571 #keep trigger level within range
572 if self[TRIGGER_LEVEL_KEY] > self.get_y_max():
573 self[TRIGGER_LEVEL_KEY] = self.get_y_max(); return
574 if self[TRIGGER_LEVEL_KEY] < self.get_y_min():
575 self[TRIGGER_LEVEL_KEY] = self.get_y_min(); return
576 #disable the trigger channel
577 if not self[TRIGGER_SHOW_KEY] or self[XY_MODE_KEY] or self[TRIGGER_MODE_KEY] == gr.gr_TRIG_MODE_FREE:
578 self.plotter.clear_waveform(channel='Trig')
579 else: #show trigger channel
580 trigger_level = self[TRIGGER_LEVEL_KEY]
581 trigger_point = (len(self.sampleses[0])-1)/self.get_actual_rate()/2.0
582 self.plotter.set_waveform(
585 [self.get_t_min(), trigger_point, trigger_point, trigger_point, trigger_point, self.get_t_max()],
586 [trigger_level, trigger_level, self.get_y_max(), self.get_y_min(), trigger_level, trigger_level]
588 color_spec=TRIGGER_COLOR_SPEC,
591 self.plotter.update()
593 def get_actual_rate(self): return 1.0*self[SAMPLE_RATE_KEY]/self[DECIMATION_KEY]
594 def get_t_min(self): return self[T_OFF_KEY]
595 def get_t_max(self): return self[T_PER_DIV_KEY]*self[T_DIVS_KEY] + self[T_OFF_KEY]
596 def get_x_min(self): return -1*self[X_PER_DIV_KEY]*self[X_DIVS_KEY]/2.0 + self[X_OFF_KEY]
597 def get_x_max(self): return self[X_PER_DIV_KEY]*self[X_DIVS_KEY]/2.0 + self[X_OFF_KEY]
598 def get_y_min(self): return -1*self[Y_PER_DIV_KEY]*self[Y_DIVS_KEY]/2.0 + self[Y_OFF_KEY]
599 def get_y_max(self): return self[Y_PER_DIV_KEY]*self[Y_DIVS_KEY]/2.0 + self[Y_OFF_KEY]
601 def update_grid(self, *args):
603 Update the grid to reflect the current settings:
604 xy divisions, xy offset, xy mode setting
606 if self[T_FRAC_OFF_KEY] < 0: self[T_FRAC_OFF_KEY] = 0; return
607 if self[T_FRAC_OFF_KEY] > 1: self[T_FRAC_OFF_KEY] = 1; return
608 if self[XY_MODE_KEY]:
610 self.plotter.set_x_label('Ch%d'%(self[X_CHANNEL_KEY]+1))
611 self.plotter.set_x_grid(self.get_x_min(), self.get_x_max(), self[X_PER_DIV_KEY])
613 self.plotter.set_y_label('Ch%d'%(self[Y_CHANNEL_KEY]+1))
614 self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY])
617 self.plotter.set_x_label('Time', 's')
618 self.plotter.set_x_grid(self.get_t_min(), self.get_t_max(), self[T_PER_DIV_KEY], True)
620 self.plotter.set_y_label('Counts')
621 self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY])
622 #redraw current sample
623 self.handle_samples()