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
34 ##################################################
36 ##################################################
37 DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'scope_rate', 30)
38 DEFAULT_WIN_SIZE = (600, 300)
44 ('Freerun', gr.gr_TRIG_MODE_FREE),
45 ('Automatic', gr.gr_TRIG_MODE_AUTO),
46 ('Normal', gr.gr_TRIG_MODE_NORM),
49 ('Positive +', gr.gr_TRIG_SLOPE_POS),
50 ('Negative -', gr.gr_TRIG_SLOPE_NEG),
52 CHANNEL_COLOR_SPECS = (
58 TRIGGER_COLOR_SPEC = (1.0, 0.4, 0.0)
59 AUTORANGE_UPDATE_RATE = 0.5 #sec
67 DEFAULT_MARKER_TYPE = None
69 ##################################################
70 # Scope window control panel
71 ##################################################
72 class control_panel(wx.Panel):
74 A control panel with wx widgits to control the plotter and scope block.
76 def __init__(self, parent):
78 Create a new control panel.
79 @param parent the wx parent window
83 wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER)
84 control_box = wx.BoxSizer(wx.VERTICAL)
85 ##################################################
87 ##################################################
88 control_box.AddStretchSpacer()
89 control_box.Add(common.LabelText(self, 'Axes Options'), 0, wx.ALIGN_CENTER)
90 control_box.AddSpacer(2)
91 ##################################################
93 ##################################################
94 scope_mode_box = wx.BoxSizer(wx.VERTICAL)
95 control_box.Add(scope_mode_box, 0, wx.EXPAND)
97 x_buttons_scope = common.IncrDecrButtons(self, self._on_incr_t_divs, self._on_decr_t_divs)
98 scope_mode_box.Add(common.LabelBox(self, 'Secs/Div', x_buttons_scope), 0, wx.EXPAND)
100 y_buttons_scope = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs)
101 parent.subscribe(AUTORANGE_KEY, lambda x: y_buttons_scope.Enable(not x))
102 scope_mode_box.Add(common.LabelBox(self, 'Counts/Div', y_buttons_scope), 0, wx.EXPAND)
104 y_off_buttons_scope = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off)
105 parent.subscribe(AUTORANGE_KEY, lambda x: y_off_buttons_scope.Enable(not x))
106 scope_mode_box.Add(common.LabelBox(self, 'Y Offset', y_off_buttons_scope), 0, wx.EXPAND)
108 scope_mode_box.AddSpacer(5)
109 t_off_slider = wx.Slider(self, size=SIZE, style=wx.SL_HORIZONTAL)
110 t_off_slider.SetRange(0, 1000)
111 def t_off_slider_changed(evt): parent[T_FRAC_OFF_KEY] = float(t_off_slider.GetValue())/t_off_slider.GetMax()
112 t_off_slider.Bind(wx.EVT_SLIDER, t_off_slider_changed)
113 parent.subscribe(T_FRAC_OFF_KEY, lambda x: t_off_slider.SetValue(int(round(x*t_off_slider.GetMax()))))
114 scope_mode_box.Add(common.LabelBox(self, 'T Offset', t_off_slider), 0, wx.EXPAND)
115 scope_mode_box.AddSpacer(5)
116 ##################################################
118 ##################################################
119 xy_mode_box = wx.BoxSizer(wx.VERTICAL)
120 control_box.Add(xy_mode_box, 0, wx.EXPAND)
122 x_buttons = common.IncrDecrButtons(self, self._on_incr_x_divs, self._on_decr_x_divs)
123 parent.subscribe(AUTORANGE_KEY, lambda x: x_buttons.Enable(not x))
124 xy_mode_box.Add(common.LabelBox(self, 'X/Div', x_buttons), 0, wx.EXPAND)
126 y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs)
127 parent.subscribe(AUTORANGE_KEY, lambda x: y_buttons.Enable(not x))
128 xy_mode_box.Add(common.LabelBox(self, 'Y/Div', y_buttons), 0, wx.EXPAND)
130 x_off_buttons = common.IncrDecrButtons(self, self._on_incr_x_off, self._on_decr_x_off)
131 parent.subscribe(AUTORANGE_KEY, lambda x: x_off_buttons.Enable(not x))
132 xy_mode_box.Add(common.LabelBox(self, 'X Off', x_off_buttons), 0, wx.EXPAND)
134 y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off)
135 parent.subscribe(AUTORANGE_KEY, lambda x: y_off_buttons.Enable(not x))
136 xy_mode_box.Add(common.LabelBox(self, 'Y Off', y_off_buttons), 0, wx.EXPAND)
137 xy_mode_box.ShowItems(False)
139 self.autorange_check_box = common.CheckBoxController(self, 'Autorange', parent, AUTORANGE_KEY)
140 control_box.Add(self.autorange_check_box, 0, wx.ALIGN_LEFT)
141 control_box.AddStretchSpacer()
142 ##################################################
144 ##################################################
145 TRIGGER_PAGE_INDEX = parent.num_inputs
146 XY_PAGE_INDEX = parent.num_inputs+1
147 control_box.Add(common.LabelText(self, 'Channel Options'), 0, wx.ALIGN_CENTER)
148 control_box.AddSpacer(2)
149 options_notebook = wx.Notebook(self)
150 control_box.Add(options_notebook, 0, wx.EXPAND)
151 def options_notebook_changed(evt):
153 parent[TRIGGER_SHOW_KEY] = options_notebook.GetSelection() == TRIGGER_PAGE_INDEX
154 parent[XY_MODE_KEY] = options_notebook.GetSelection() == XY_PAGE_INDEX
155 except wx.PyDeadObjectError: pass
156 options_notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, options_notebook_changed)
157 def xy_mode_changed(mode):
158 #ensure xy tab is selected
159 if mode and options_notebook.GetSelection() != XY_PAGE_INDEX:
160 options_notebook.SetSelection(XY_PAGE_INDEX)
161 #ensure xy tab is not selected
162 elif not mode and options_notebook.GetSelection() == XY_PAGE_INDEX:
163 options_notebook.SetSelection(0)
164 #show/hide control buttons
165 scope_mode_box.ShowItems(not mode)
166 xy_mode_box.ShowItems(mode)
168 parent.subscribe(XY_MODE_KEY, xy_mode_changed)
169 ##################################################
171 ##################################################
172 for i in range(parent.num_inputs):
173 channel_menu_panel = wx.Panel(options_notebook)
174 options_notebook.AddPage(channel_menu_panel, 'Ch%d'%(i+1))
175 channel_menu_box = wx.BoxSizer(wx.VERTICAL)
176 channel_menu_panel.SetSizer(channel_menu_box)
178 channel_menu_box.AddStretchSpacer()
179 coupling_chooser = common.DropDownController(channel_menu_panel, COUPLING_MODES, parent, common.index_key(AC_COUPLE_KEY, i), SIZE)
180 channel_menu_box.Add(common.LabelBox(channel_menu_panel, 'Coupling', coupling_chooser), 0, wx.EXPAND)
182 channel_menu_box.AddStretchSpacer()
183 marker_chooser = common.DropDownController(channel_menu_panel, MARKER_TYPES, parent, common.index_key(MARKER_KEY, i), SIZE)
184 channel_menu_box.Add(common.LabelBox(channel_menu_panel, 'Marker', marker_chooser), 0, wx.EXPAND)
185 channel_menu_box.AddStretchSpacer()
186 ##################################################
188 ##################################################
189 trigger_menu_panel = wx.Panel(options_notebook)
190 options_notebook.AddPage(trigger_menu_panel, 'Trig')
191 trigger_menu_box = wx.BoxSizer(wx.VERTICAL)
192 trigger_menu_panel.SetSizer(trigger_menu_box)
194 trigger_mode_chooser = common.DropDownController(trigger_menu_panel, TRIGGER_MODES, parent, TRIGGER_MODE_KEY, SIZE)
195 trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Mode', trigger_mode_chooser), 0, wx.EXPAND)
197 trigger_slope_chooser = common.DropDownController(trigger_menu_panel, TRIGGER_SLOPES, parent, TRIGGER_SLOPE_KEY, SIZE)
198 parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_slope_chooser.Enable(x!=gr.gr_TRIG_MODE_FREE))
199 trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Slope', trigger_slope_chooser), 0, wx.EXPAND)
201 choices = [('Channel %d'%(i+1), i) for i in range(parent.num_inputs)]
202 trigger_channel_chooser = common.DropDownController(trigger_menu_panel, choices, parent, TRIGGER_CHANNEL_KEY, SIZE)
203 parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_channel_chooser.Enable(x!=gr.gr_TRIG_MODE_FREE))
204 trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Channel', trigger_channel_chooser), 0, wx.EXPAND)
206 hbox = wx.BoxSizer(wx.HORIZONTAL)
207 trigger_menu_box.Add(hbox, 0, wx.EXPAND)
208 hbox.Add(wx.StaticText(trigger_menu_panel, label=' Level '), 1, wx.ALIGN_CENTER_VERTICAL)
209 trigger_level_button = wx.Button(trigger_menu_panel, label='50%', style=wx.BU_EXACTFIT)
210 parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_level_button.Enable(x!=gr.gr_TRIG_MODE_FREE))
211 trigger_level_button.Bind(wx.EVT_BUTTON, self.parent.set_auto_trigger_level)
212 hbox.Add(trigger_level_button, 0, wx.ALIGN_CENTER_VERTICAL)
214 trigger_level_buttons = common.IncrDecrButtons(trigger_menu_panel, self._on_incr_trigger_level, self._on_decr_trigger_level)
215 parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_level_buttons.Enable(x!=gr.gr_TRIG_MODE_FREE))
216 hbox.Add(trigger_level_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
217 ##################################################
219 ##################################################
220 if parent.num_inputs > 1:
221 xy_menu_panel = wx.Panel(options_notebook)
222 options_notebook.AddPage(xy_menu_panel, 'XY')
223 xy_menu_box = wx.BoxSizer(wx.VERTICAL)
224 xy_menu_panel.SetSizer(xy_menu_box)
225 #x and y channel choosers
226 xy_menu_box.AddStretchSpacer()
227 choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)]
228 x_channel_chooser = common.DropDownController(xy_menu_panel, choices, parent, X_CHANNEL_KEY, SIZE)
229 xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Ch X', x_channel_chooser), 0, wx.EXPAND)
230 xy_menu_box.AddStretchSpacer()
231 y_channel_chooser = common.DropDownController(xy_menu_panel, choices, parent, Y_CHANNEL_KEY, SIZE)
232 xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Ch Y', y_channel_chooser), 0, wx.EXPAND)
234 xy_menu_box.AddStretchSpacer()
235 marker_chooser = common.DropDownController(xy_menu_panel, MARKER_TYPES, parent, XY_MARKER_KEY, SIZE)
236 xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Marker', marker_chooser), 0, wx.EXPAND)
237 xy_menu_box.AddStretchSpacer()
238 ##################################################
240 ##################################################
242 self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run')
243 control_box.Add(self.run_button, 0, wx.EXPAND)
245 self.SetSizerAndFit(control_box)
247 def on_mouse_wheel(event):
248 if not parent[XY_MODE_KEY]:
249 if event.GetWheelRotation() < 0: self._on_incr_t_divs(event)
250 else: self._on_decr_t_divs(event)
251 parent.plotter.Bind(wx.EVT_MOUSEWHEEL, on_mouse_wheel)
253 ##################################################
255 ##################################################
257 def _on_incr_trigger_level(self, event):
258 self.parent[TRIGGER_LEVEL_KEY] += self.parent[Y_PER_DIV_KEY]/3.
259 def _on_decr_trigger_level(self, event):
260 self.parent[TRIGGER_LEVEL_KEY] -= self.parent[Y_PER_DIV_KEY]/3.
262 def _on_incr_t_divs(self, event):
263 self.parent[T_PER_DIV_KEY] = common.get_clean_incr(self.parent[T_PER_DIV_KEY])
264 def _on_decr_t_divs(self, event):
265 self.parent[T_PER_DIV_KEY] = common.get_clean_decr(self.parent[T_PER_DIV_KEY])
266 def _on_incr_x_divs(self, event):
267 self.parent[X_PER_DIV_KEY] = common.get_clean_incr(self.parent[X_PER_DIV_KEY])
268 def _on_decr_x_divs(self, event):
269 self.parent[X_PER_DIV_KEY] = common.get_clean_decr(self.parent[X_PER_DIV_KEY])
270 def _on_incr_y_divs(self, event):
271 self.parent[Y_PER_DIV_KEY] = common.get_clean_incr(self.parent[Y_PER_DIV_KEY])
272 def _on_decr_y_divs(self, event):
273 self.parent[Y_PER_DIV_KEY] = common.get_clean_decr(self.parent[Y_PER_DIV_KEY])
275 def _on_incr_x_off(self, event):
276 self.parent[X_OFF_KEY] = self.parent[X_OFF_KEY] + self.parent[X_PER_DIV_KEY]
277 def _on_decr_x_off(self, event):
278 self.parent[X_OFF_KEY] = self.parent[X_OFF_KEY] - self.parent[X_PER_DIV_KEY]
279 def _on_incr_y_off(self, event):
280 self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] + self.parent[Y_PER_DIV_KEY]
281 def _on_decr_y_off(self, event):
282 self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY]
284 ##################################################
285 # Scope window with plotter and control panel
286 ##################################################
287 class scope_window(wx.Panel, pubsub.pubsub):
308 pubsub.pubsub.__init__(self)
310 assert num_inputs <= len(CHANNEL_COLOR_SPECS)
312 self.sampleses = None
313 self.num_inputs = num_inputs
314 autorange = not v_scale
315 self.autorange_ts = 0
316 v_scale = v_scale or 1
317 self.frame_rate_ts = 0
319 self.proxy(MSG_KEY, controller, msg_key)
320 self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key)
321 self.proxy(TRIGGER_LEVEL_KEY, controller, trigger_level_key)
322 self.proxy(TRIGGER_MODE_KEY, controller, trigger_mode_key)
323 self.proxy(TRIGGER_SLOPE_KEY, controller, trigger_slope_key)
324 self.proxy(TRIGGER_CHANNEL_KEY, controller, trigger_channel_key)
325 self.proxy(DECIMATION_KEY, controller, decimation_key)
326 for i in range(num_inputs):
327 self.proxy(common.index_key(AC_COUPLE_KEY, i), controller, common.index_key(ac_couple_key, i))
329 wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER)
330 self.plotter = plotter.channel_plotter(self)
331 self.plotter.SetSize(wx.Size(*size))
332 self.plotter.set_title(title)
333 self.plotter.enable_legend(True)
334 self.plotter.enable_point_label(True)
335 self.plotter.enable_grid_lines(True)
336 #setup the box with plot and controls
337 self.control_panel = control_panel(self)
338 main_box = wx.BoxSizer(wx.HORIZONTAL)
339 main_box.Add(self.plotter, 1, wx.EXPAND)
340 main_box.Add(self.control_panel, 0, wx.EXPAND)
341 self.SetSizerAndFit(main_box)
343 self[RUNNING_KEY] = True
344 for i in range(self.num_inputs):
345 self[common.index_key(AC_COUPLE_KEY, i)] = self[common.index_key(AC_COUPLE_KEY, i)]
346 self[common.index_key(MARKER_KEY, i)] = DEFAULT_MARKER_TYPE
347 self[XY_MARKER_KEY] = 2.0
348 self[XY_MODE_KEY] = xy_mode
349 self[X_CHANNEL_KEY] = 0
350 self[Y_CHANNEL_KEY] = self.num_inputs-1
351 self[AUTORANGE_KEY] = autorange
352 self[T_PER_DIV_KEY] = t_scale
353 self[X_PER_DIV_KEY] = v_scale
354 self[Y_PER_DIV_KEY] = v_scale
361 self[FRAME_RATE_KEY] = frame_rate
362 self[TRIGGER_LEVEL_KEY] = 0
363 self[TRIGGER_CHANNEL_KEY] = 0
364 self[TRIGGER_MODE_KEY] = gr.gr_TRIG_MODE_AUTO
365 self[TRIGGER_SLOPE_KEY] = gr.gr_TRIG_SLOPE_POS
366 self[T_FRAC_OFF_KEY] = 0.5
367 #register events for message
368 self.subscribe(MSG_KEY, self.handle_msg)
369 #register events for grid
370 for key in [common.index_key(MARKER_KEY, i) for i in range(self.num_inputs)] + [
371 TRIGGER_LEVEL_KEY, TRIGGER_MODE_KEY,
372 T_PER_DIV_KEY, X_PER_DIV_KEY, Y_PER_DIV_KEY,
373 T_OFF_KEY, X_OFF_KEY, Y_OFF_KEY,
374 T_DIVS_KEY, X_DIVS_KEY, Y_DIVS_KEY,
375 XY_MODE_KEY, AUTORANGE_KEY, T_FRAC_OFF_KEY,
376 TRIGGER_SHOW_KEY, XY_MARKER_KEY, X_CHANNEL_KEY, Y_CHANNEL_KEY,
377 ]: self.subscribe(key, self.update_grid)
381 def handle_msg(self, msg):
383 Handle the message from the scope sink message queue.
384 Plot the list of arrays of samples onto the grid.
385 Each samples array gets its own channel.
386 @param msg the time domain data as a character array
388 if not self[RUNNING_KEY]: return
390 if time.time() - self.frame_rate_ts < 1.0/self[FRAME_RATE_KEY]: return
391 #convert to floating point numbers
392 samples = numpy.fromstring(msg, numpy.float32)
393 #extract the trigger offset
394 self.trigger_offset = samples[-1]
395 samples = samples[:-1]
396 samps_per_ch = len(samples)/self.num_inputs
397 self.sampleses = [samples[samps_per_ch*i:samps_per_ch*(i+1)] for i in range(self.num_inputs)]
399 self.handle_samples()
400 self.frame_rate_ts = time.time()
402 def set_auto_trigger_level(self, *args):
404 Use the current trigger channel and samples to calculate the 50% level.
406 if not self.sampleses: return
407 samples = self.sampleses[self[TRIGGER_CHANNEL_KEY]]
408 self[TRIGGER_LEVEL_KEY] = (numpy.max(samples)+numpy.min(samples))/2
410 def handle_samples(self):
412 Handle the cached samples from the scope input.
413 Perform ac coupling, triggering, and auto ranging.
415 if not self.sampleses: return
416 sampleses = self.sampleses
417 if self[XY_MODE_KEY]:
418 self[DECIMATION_KEY] = 1
419 x_samples = sampleses[self[X_CHANNEL_KEY]]
420 y_samples = sampleses[self[Y_CHANNEL_KEY]]
422 if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
423 x_min, x_max = common.get_min_max(x_samples)
424 y_min, y_max = common.get_min_max(y_samples)
425 #adjust the x per div
426 x_per_div = common.get_clean_num((x_max-x_min)/self[X_DIVS_KEY])
427 if x_per_div != self[X_PER_DIV_KEY]: self[X_PER_DIV_KEY] = x_per_div; return
429 x_off = x_per_div*round((x_max+x_min)/2/x_per_div)
430 if x_off != self[X_OFF_KEY]: self[X_OFF_KEY] = x_off; return
431 #adjust the y per div
432 y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
433 if y_per_div != self[Y_PER_DIV_KEY]: self[Y_PER_DIV_KEY] = y_per_div; return
435 y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
436 if y_off != self[Y_OFF_KEY]: self[Y_OFF_KEY] = y_off; return
437 self.autorange_ts = time.time()
439 self.plotter.set_waveform(
441 samples=(x_samples, y_samples),
442 color_spec=CHANNEL_COLOR_SPECS[0],
443 marker=self[XY_MARKER_KEY],
445 #turn off each waveform
446 for i, samples in enumerate(sampleses):
447 self.plotter.clear_waveform(channel='Ch%d'%(i+1))
450 if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
451 bounds = [common.get_min_max(samples) for samples in sampleses]
452 y_min = numpy.min([bound[0] for bound in bounds])
453 y_max = numpy.max([bound[1] for bound in bounds])
454 #adjust the y per div
455 y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
456 if y_per_div != self[Y_PER_DIV_KEY]: self[Y_PER_DIV_KEY] = y_per_div; return
458 y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
459 if y_off != self[Y_OFF_KEY]: self[Y_OFF_KEY] = y_off; return
460 self.autorange_ts = time.time()
461 #number of samples to scale to the screen
462 actual_rate = self.get_actual_rate()
463 time_span = self[T_PER_DIV_KEY]*self[T_DIVS_KEY]
464 num_samps = int(round(time_span*actual_rate))
465 #handle the time offset
466 t_off = self[T_FRAC_OFF_KEY]*(len(sampleses[0])/actual_rate - time_span)
467 if t_off != self[T_OFF_KEY]: self[T_OFF_KEY] = t_off; return
468 samps_off = int(round(actual_rate*self[T_OFF_KEY]))
469 #adjust the decim so that we use about half the samps
470 self[DECIMATION_KEY] = int(round(
471 time_span*self[SAMPLE_RATE_KEY]/(0.5*len(sampleses[0]))
474 #num samps too small, auto increment the time
475 if num_samps < 2: self[T_PER_DIV_KEY] = common.get_clean_incr(self[T_PER_DIV_KEY])
476 #num samps in bounds, plot each waveform
477 elif num_samps <= len(sampleses[0]):
478 for i, samples in enumerate(sampleses):
480 self.plotter.set_waveform(
481 channel='Ch%d'%(i+1),
482 samples=samples[samps_off:num_samps+samps_off],
483 color_spec=CHANNEL_COLOR_SPECS[i],
484 marker=self[common.index_key(MARKER_KEY, i)],
485 trig_off=self.trigger_offset,
488 self.plotter.clear_waveform(channel='XY')
489 #keep trigger level within range
490 if self[TRIGGER_LEVEL_KEY] > self.get_y_max():
491 self[TRIGGER_LEVEL_KEY] = self.get_y_max(); return
492 if self[TRIGGER_LEVEL_KEY] < self.get_y_min():
493 self[TRIGGER_LEVEL_KEY] = self.get_y_min(); return
494 #disable the trigger channel
495 if not self[TRIGGER_SHOW_KEY] or self[XY_MODE_KEY] or self[TRIGGER_MODE_KEY] == gr.gr_TRIG_MODE_FREE:
496 self.plotter.clear_waveform(channel='Trig')
497 else: #show trigger channel
498 trigger_level = self[TRIGGER_LEVEL_KEY]
499 trigger_point = (len(self.sampleses[0])-1)/self.get_actual_rate()/2.0
500 self.plotter.set_waveform(
503 [self.get_t_min(), trigger_point, trigger_point, trigger_point, trigger_point, self.get_t_max()],
504 [trigger_level, trigger_level, self.get_y_max(), self.get_y_min(), trigger_level, trigger_level]
506 color_spec=TRIGGER_COLOR_SPEC,
509 self.plotter.update()
511 def get_actual_rate(self): return 1.0*self[SAMPLE_RATE_KEY]/self[DECIMATION_KEY]
512 def get_t_min(self): return self[T_OFF_KEY]
513 def get_t_max(self): return self[T_PER_DIV_KEY]*self[T_DIVS_KEY] + self[T_OFF_KEY]
514 def get_x_min(self): return -1*self[X_PER_DIV_KEY]*self[X_DIVS_KEY]/2.0 + self[X_OFF_KEY]
515 def get_x_max(self): return self[X_PER_DIV_KEY]*self[X_DIVS_KEY]/2.0 + self[X_OFF_KEY]
516 def get_y_min(self): return -1*self[Y_PER_DIV_KEY]*self[Y_DIVS_KEY]/2.0 + self[Y_OFF_KEY]
517 def get_y_max(self): return self[Y_PER_DIV_KEY]*self[Y_DIVS_KEY]/2.0 + self[Y_OFF_KEY]
519 def update_grid(self, *args):
521 Update the grid to reflect the current settings:
522 xy divisions, xy offset, xy mode setting
524 if self[T_FRAC_OFF_KEY] < 0: self[T_FRAC_OFF_KEY] = 0; return
525 if self[T_FRAC_OFF_KEY] > 1: self[T_FRAC_OFF_KEY] = 1; return
526 if self[XY_MODE_KEY]:
528 self.plotter.set_x_label('Ch%d'%(self[X_CHANNEL_KEY]+1))
529 self.plotter.set_x_grid(self.get_x_min(), self.get_x_max(), self[X_PER_DIV_KEY])
531 self.plotter.set_y_label('Ch%d'%(self[Y_CHANNEL_KEY]+1))
532 self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY])
535 self.plotter.set_x_label('Time', 's')
536 self.plotter.set_x_grid(self.get_t_min(), self.get_t_max(), self[T_PER_DIV_KEY], True)
538 self.plotter.set_y_label('Counts')
539 self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY])
540 #redraw current sample
541 self.handle_samples()