7d4f97113e12b5fbf7a8db5e5a528c8dc8346fa5
[debian/gnuradio] / gr-wxgui / src / python / scope_window.py
1 #
2 # Copyright 2008 Free Software Foundation, Inc.
3 #
4 # This file is part of GNU Radio
5 #
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)
9 # any later version.
10 #
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.
15 #
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.
20 #
21
22 ##################################################
23 # Imports
24 ##################################################
25 import plotter
26 import common
27 import wx
28 import numpy
29 import time
30 import pubsub
31 from constants import *
32 from gnuradio import gr #for gr.prefs
33
34 ##################################################
35 # Constants
36 ##################################################
37 DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'scope_rate', 30)
38 DEFAULT_WIN_SIZE = (600, 300)
39 DEFAULT_V_SCALE = 1000
40 TRIGGER_MODES = (
41         ('Off', 0),
42         ('Neg', -1),
43         ('Pos', +1),
44 )
45 TRIGGER_LEVELS = (
46         ('Auto', None),
47         ('+High', 0.75),
48         ('+Med', 0.5),
49         ('+Low', 0.25),
50         ('Zero', 0.0),
51         ('-Low', -0.25),
52         ('-Med', -0.5),
53         ('-High', -0.75),
54 )
55 CHANNEL_COLOR_SPECS = (
56         (0, 0, 1),
57         (0, 1, 0),
58         (1, 0, 0),
59         (1, 0, 1),
60 )
61 AUTORANGE_UPDATE_RATE = 0.5 #sec
62 MARKER_TYPES = (
63         ('Dot Small', 1.0),
64         ('Dot Medium', 2.0),
65         ('Dot Large', 3.0),
66         ('Line Link', None),
67 )
68 DEFAULT_MARKER_TYPE = None
69
70 ##################################################
71 # Scope window control panel
72 ##################################################
73 class control_panel(wx.Panel):
74         """
75         A control panel with wx widgits to control the plotter and scope block.
76         """
77         def __init__(self, parent):
78                 """
79                 Create a new control panel.
80                 @param parent the wx parent window
81                 """
82                 self.parent = parent
83                 wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER)
84                 self.control_box = control_box = wx.BoxSizer(wx.VERTICAL)
85                 #trigger options
86                 control_box.AddStretchSpacer()
87                 control_box.Add(common.LabelText(self, 'Trigger Options'), 0, wx.ALIGN_CENTER)
88                 control_box.AddSpacer(2)
89                 #trigger mode
90                 self.trigger_mode_chooser = common.DropDownController(self, 'Mode', TRIGGER_MODES, parent, TRIGGER_MODE_KEY)
91                 control_box.Add(self.trigger_mode_chooser, 0, wx.EXPAND)
92                 #trigger level
93                 self.trigger_level_chooser = common.DropDownController(self, 'Level', TRIGGER_LEVELS, parent, TRIGGER_LEVEL_KEY)
94                 parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_level_chooser.Disable(x==0))
95                 control_box.Add(self.trigger_level_chooser, 0, wx.EXPAND)
96                 #trigger channel
97                 choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)]
98                 self.trigger_channel_chooser = common.DropDownController(self, 'Channel', choices, parent, TRIGGER_CHANNEL_KEY)
99                 parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_channel_chooser.Disable(x==0))
100                 control_box.Add(self.trigger_channel_chooser, 0, wx.EXPAND)
101                 #axes options
102                 SPACING = 15
103                 control_box.AddStretchSpacer()
104                 control_box.Add(common.LabelText(self, 'Axes Options'), 0, wx.ALIGN_CENTER)
105                 control_box.AddSpacer(2)
106                 ##################################################
107                 # Scope Mode Box
108                 ##################################################
109                 self.scope_mode_box = wx.BoxSizer(wx.VERTICAL)
110                 control_box.Add(self.scope_mode_box, 0, wx.EXPAND)
111                 #x axis divs
112                 hbox = wx.BoxSizer(wx.HORIZONTAL)
113                 self.scope_mode_box.Add(hbox, 0, wx.EXPAND)
114                 hbox.Add(wx.StaticText(self, -1, ' Secs/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
115                 x_buttons = common.IncrDecrButtons(self, self._on_incr_t_divs, self._on_decr_t_divs)
116                 hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
117                 hbox.AddSpacer(SPACING)
118                 #y axis divs
119                 hbox = wx.BoxSizer(wx.HORIZONTAL)
120                 self.scope_mode_box.Add(hbox, 0, wx.EXPAND)
121                 hbox.Add(wx.StaticText(self, -1, ' Units/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
122                 y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs)
123                 parent.subscribe(AUTORANGE_KEY, y_buttons.Disable)
124                 hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
125                 hbox.AddSpacer(SPACING)
126                 #y axis ref lvl
127                 hbox = wx.BoxSizer(wx.HORIZONTAL)
128                 self.scope_mode_box.Add(hbox, 0, wx.EXPAND)
129                 hbox.Add(wx.StaticText(self, -1, ' Y Offset '), 1, wx.ALIGN_CENTER_VERTICAL)
130                 y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off)
131                 parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable)
132                 hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
133                 hbox.AddSpacer(SPACING)
134                 ##################################################
135                 # XY Mode Box
136                 ##################################################
137                 self.xy_mode_box = wx.BoxSizer(wx.VERTICAL)
138                 control_box.Add(self.xy_mode_box, 0, wx.EXPAND)
139                 #x and y channel
140                 CHOOSER_WIDTH = 60
141                 CENTER_SPACING = 10
142                 hbox = wx.BoxSizer(wx.HORIZONTAL)
143                 self.xy_mode_box.Add(hbox, 0, wx.EXPAND)
144                 choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)]
145                 self.channel_x_chooser = common.DropDownController(self, 'X Ch', choices, parent, SCOPE_X_CHANNEL_KEY, (CHOOSER_WIDTH, -1))
146                 hbox.Add(self.channel_x_chooser, 0, wx.EXPAND)
147                 hbox.AddSpacer(CENTER_SPACING)
148                 self.channel_y_chooser = common.DropDownController(self, 'Y Ch', choices, parent, SCOPE_Y_CHANNEL_KEY, (CHOOSER_WIDTH, -1))
149                 hbox.Add(self.channel_y_chooser, 0, wx.EXPAND)
150                 #div controls
151                 hbox = wx.BoxSizer(wx.HORIZONTAL)
152                 self.xy_mode_box.Add(hbox, 0, wx.EXPAND)
153                 hbox.Add(wx.StaticText(self, -1, ' X/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
154                 x_buttons = common.IncrDecrButtons(self, self._on_incr_x_divs, self._on_decr_x_divs)
155                 parent.subscribe(AUTORANGE_KEY, x_buttons.Disable)
156                 hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
157                 hbox.AddSpacer(CENTER_SPACING)
158                 hbox.Add(wx.StaticText(self, -1, ' Y/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
159                 y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs)
160                 parent.subscribe(AUTORANGE_KEY, y_buttons.Disable)
161                 hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
162                 #offset controls
163                 hbox = wx.BoxSizer(wx.HORIZONTAL)
164                 self.xy_mode_box.Add(hbox, 0, wx.EXPAND)
165                 hbox.Add(wx.StaticText(self, -1, ' X Off '), 1, wx.ALIGN_CENTER_VERTICAL)
166                 x_off_buttons = common.IncrDecrButtons(self, self._on_incr_x_off, self._on_decr_x_off)
167                 parent.subscribe(AUTORANGE_KEY, x_off_buttons.Disable)
168                 hbox.Add(x_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
169                 hbox.AddSpacer(CENTER_SPACING)
170                 hbox.Add(wx.StaticText(self, -1, ' Y Off '), 1, wx.ALIGN_CENTER_VERTICAL)
171                 y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off)
172                 parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable)
173                 hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
174                 ##################################################
175                 # End Special Boxes
176                 ##################################################
177                 #misc options
178                 control_box.AddStretchSpacer()
179                 control_box.Add(common.LabelText(self, 'Range Options'), 0, wx.ALIGN_CENTER)
180                 #ac couple check box
181                 self.ac_couple_check_box = common.CheckBoxController(self, 'AC Couple', parent, AC_COUPLE_KEY)
182                 control_box.Add(self.ac_couple_check_box, 0, wx.ALIGN_LEFT)
183                 #autorange check box
184                 self.autorange_check_box = common.CheckBoxController(self, 'Autorange', parent, AUTORANGE_KEY)
185                 control_box.Add(self.autorange_check_box, 0, wx.ALIGN_LEFT)
186                 #marker
187                 control_box.AddStretchSpacer()
188                 self.marker_chooser = common.DropDownController(self, 'Marker', MARKER_TYPES, parent, MARKER_KEY)
189                 control_box.Add(self.marker_chooser, 0, wx.EXPAND)
190                 #xy mode
191                 control_box.AddStretchSpacer()
192                 self.scope_xy_mode_button = common.ToggleButtonController(self, parent, SCOPE_XY_MODE_KEY, 'Scope Mode', 'X:Y Mode')
193                 parent.subscribe(SCOPE_XY_MODE_KEY, self._on_scope_xy_mode)
194                 control_box.Add(self.scope_xy_mode_button, 0, wx.EXPAND)
195                 #run/stop
196                 self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run')
197                 control_box.Add(self.run_button, 0, wx.EXPAND)
198                 #set sizer
199                 self.SetSizerAndFit(control_box)
200
201         ##################################################
202         # Event handlers
203         ##################################################
204         def _on_scope_xy_mode(self, mode):
205                 self.scope_mode_box.ShowItems(not mode)
206                 self.xy_mode_box.ShowItems(mode)
207                 self.control_box.Layout()
208         #incr/decr divs
209         def _on_incr_t_divs(self, event):
210                 self.parent.set_t_per_div(
211                         common.get_clean_incr(self.parent[T_PER_DIV_KEY]))
212         def _on_decr_t_divs(self, event):
213                 self.parent.set_t_per_div(
214                         common.get_clean_decr(self.parent[T_PER_DIV_KEY]))
215         def _on_incr_x_divs(self, event):
216                 self.parent.set_x_per_div(
217                         common.get_clean_incr(self.parent[X_PER_DIV_KEY]))
218         def _on_decr_x_divs(self, event):
219                 self.parent.set_x_per_div(
220                         common.get_clean_decr(self.parent[X_PER_DIV_KEY]))
221         def _on_incr_y_divs(self, event):
222                 self.parent.set_y_per_div(
223                         common.get_clean_incr(self.parent[Y_PER_DIV_KEY]))
224         def _on_decr_y_divs(self, event):
225                 self.parent.set_y_per_div(
226                         common.get_clean_decr(self.parent[Y_PER_DIV_KEY]))
227         #incr/decr offset
228         def _on_incr_t_off(self, event):
229                 self.parent.set_t_off(
230                         self.parent[T_OFF_KEY] + self.parent[T_PER_DIV_KEY])
231         def _on_decr_t_off(self, event):
232                 self.parent.set_t_off(
233                         self.parent[T_OFF_KEY] - self.parent[T_PER_DIV_KEY])
234         def _on_incr_x_off(self, event):
235                 self.parent.set_x_off(
236                         self.parent[X_OFF_KEY] + self.parent[X_PER_DIV_KEY])
237         def _on_decr_x_off(self, event):
238                 self.parent.set_x_off(
239                         self.parent[X_OFF_KEY] - self.parent[X_PER_DIV_KEY])
240         def _on_incr_y_off(self, event):
241                 self.parent.set_y_off(
242                         self.parent[Y_OFF_KEY] + self.parent[Y_PER_DIV_KEY])
243         def _on_decr_y_off(self, event):
244                 self.parent.set_y_off(
245                         self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY])
246
247 ##################################################
248 # Scope window with plotter and control panel
249 ##################################################
250 class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter):
251         def __init__(
252                 self,
253                 parent,
254                 controller,
255                 size,
256                 title,
257                 frame_rate,
258                 num_inputs,
259                 sample_rate_key,
260                 t_scale,
261                 v_scale,
262                 ac_couple,
263                 xy_mode,
264                 scope_trigger_level_key,
265                 scope_trigger_mode_key,
266                 scope_trigger_channel_key,
267                 msg_key,
268         ):
269                 pubsub.pubsub.__init__(self)
270                 #check num inputs
271                 assert num_inputs <= len(CHANNEL_COLOR_SPECS)
272                 #setup
273                 self.sampleses = None
274                 self.ext_controller = controller
275                 self.num_inputs = num_inputs
276                 self.sample_rate_key = sample_rate_key
277                 autorange = v_scale is None
278                 self.autorange_ts = 0
279                 if v_scale is None: v_scale = 1
280                 self.frame_rate_ts = 0
281                 self._init = False #HACK
282                 #scope keys
283                 self.scope_trigger_level_key = scope_trigger_level_key
284                 self.scope_trigger_mode_key = scope_trigger_mode_key
285                 self.scope_trigger_channel_key = scope_trigger_channel_key
286                 #init panel and plot
287                 wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)
288                 self.plotter = plotter.channel_plotter(self)
289                 self.plotter.SetSize(wx.Size(*size))
290                 self.plotter.set_title(title)
291                 self.plotter.enable_legend(True)
292                 self.plotter.enable_point_label(True)
293                 #setup the box with plot and controls
294                 self.control_panel = control_panel(self)
295                 main_box = wx.BoxSizer(wx.HORIZONTAL)
296                 main_box.Add(self.plotter, 1, wx.EXPAND)
297                 main_box.Add(self.control_panel, 0, wx.EXPAND)
298                 self.SetSizerAndFit(main_box)
299                 #initial setup
300                 self._register_set_prop(self, RUNNING_KEY, True)
301                 self._register_set_prop(self, AC_COUPLE_KEY, ac_couple)
302                 self._register_set_prop(self, SCOPE_XY_MODE_KEY, xy_mode)
303                 self._register_set_prop(self, AUTORANGE_KEY, autorange)
304                 self._register_set_prop(self, T_PER_DIV_KEY, t_scale)
305                 self._register_set_prop(self, X_PER_DIV_KEY, v_scale)
306                 self._register_set_prop(self, Y_PER_DIV_KEY, v_scale)
307                 self._register_set_prop(self, T_OFF_KEY, 0)
308                 self._register_set_prop(self, X_OFF_KEY, 0)
309                 self._register_set_prop(self, Y_OFF_KEY, 0)
310                 self._register_set_prop(self, T_DIVS_KEY, 8)
311                 self._register_set_prop(self, X_DIVS_KEY, 8)
312                 self._register_set_prop(self, Y_DIVS_KEY, 8)
313                 self._register_set_prop(self, SCOPE_X_CHANNEL_KEY, 0)
314                 self._register_set_prop(self, SCOPE_Y_CHANNEL_KEY, num_inputs-1)
315                 self._register_set_prop(self, FRAME_RATE_KEY, frame_rate)
316                 self._register_set_prop(self, TRIGGER_CHANNEL_KEY, 0)
317                 self._register_set_prop(self, TRIGGER_MODE_KEY, 1)
318                 self._register_set_prop(self, TRIGGER_LEVEL_KEY, None)
319                 self._register_set_prop(self, MARKER_KEY, DEFAULT_MARKER_TYPE)
320                 #register events
321                 self.ext_controller.subscribe(msg_key, self.handle_msg)
322                 for key in (
323                         T_PER_DIV_KEY, X_PER_DIV_KEY, Y_PER_DIV_KEY,
324                         T_OFF_KEY, X_OFF_KEY, Y_OFF_KEY,
325                         T_DIVS_KEY, X_DIVS_KEY, Y_DIVS_KEY,
326                         SCOPE_XY_MODE_KEY,
327                         SCOPE_X_CHANNEL_KEY,
328                         SCOPE_Y_CHANNEL_KEY,
329                         AUTORANGE_KEY,
330                         AC_COUPLE_KEY,
331                         MARKER_KEY,
332                 ): self.subscribe(key, self.update_grid)
333                 #initial update, dont do this here, wait for handle_msg #HACK
334                 #self.update_grid()
335
336         def handle_msg(self, msg):
337                 """
338                 Handle the message from the scope sink message queue.
339                 Plot the list of arrays of samples onto the grid.
340                 Each samples array gets its own channel.
341                 @param msg the time domain data as a character array
342                 """
343                 if not self[RUNNING_KEY]: return
344                 #check time elapsed
345                 if time.time() - self.frame_rate_ts < 1.0/self[FRAME_RATE_KEY]: return
346                 #convert to floating point numbers
347                 samples = numpy.fromstring(msg, numpy.float32)
348                 samps_per_ch = len(samples)/self.num_inputs
349                 self.sampleses = [samples[samps_per_ch*i:samps_per_ch*(i+1)] for i in range(self.num_inputs)]
350                 if not self._init: #HACK
351                         self._init = True
352                         self.update_grid()
353                 #handle samples
354                 self.handle_samples()
355                 self.frame_rate_ts = time.time()
356
357         def handle_samples(self):
358                 """
359                 Handle the cached samples from the scope input.
360                 Perform ac coupling, triggering, and auto ranging.
361                 """
362                 if not self.sampleses: return
363                 sampleses = self.sampleses
364                 #trigger level (must do before ac coupling)
365                 self.ext_controller[self.scope_trigger_channel_key] = self[TRIGGER_CHANNEL_KEY]
366                 self.ext_controller[self.scope_trigger_mode_key] = self[TRIGGER_MODE_KEY]
367                 trigger_level = self[TRIGGER_LEVEL_KEY]
368                 if trigger_level is None: self.ext_controller[self.scope_trigger_level_key] = ''
369                 else:
370                         samples = sampleses[self[TRIGGER_CHANNEL_KEY]]
371                         self.ext_controller[self.scope_trigger_level_key] = \
372                         trigger_level*(numpy.max(samples)-numpy.min(samples))/2 + numpy.average(samples)
373                 #ac coupling
374                 if self[AC_COUPLE_KEY]:
375                         sampleses = [samples - numpy.average(samples) for samples in sampleses]
376                 if self[SCOPE_XY_MODE_KEY]:
377                         x_samples = sampleses[self[SCOPE_X_CHANNEL_KEY]]
378                         y_samples = sampleses[self[SCOPE_Y_CHANNEL_KEY]]
379                         #autorange
380                         if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
381                                 x_min, x_max = common.get_min_max(x_samples)
382                                 y_min, y_max = common.get_min_max(y_samples)
383                                 #adjust the x per div
384                                 x_per_div = common.get_clean_num((x_max-x_min)/self[X_DIVS_KEY])
385                                 if x_per_div != self[X_PER_DIV_KEY]: self.set_x_per_div(x_per_div)
386                                 #adjust the x offset
387                                 x_off = x_per_div*round((x_max+x_min)/2/x_per_div)
388                                 if x_off != self[X_OFF_KEY]: self.set_x_off(x_off)
389                                 #adjust the y per div
390                                 y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
391                                 if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div)
392                                 #adjust the y offset
393                                 y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
394                                 if y_off != self[Y_OFF_KEY]: self.set_y_off(y_off)
395                                 self.autorange_ts = time.time()
396                         #plot xy channel
397                         self.plotter.set_waveform(
398                                 channel='XY',
399                                 samples=(x_samples, y_samples),
400                                 color_spec=CHANNEL_COLOR_SPECS[0],
401                                 marker=self[MARKER_KEY],
402                         )
403                         #turn off each waveform
404                         for i, samples in enumerate(sampleses):
405                                 self.plotter.set_waveform(
406                                         channel='Ch%d'%(i+1),
407                                         samples=[],
408                                         color_spec=CHANNEL_COLOR_SPECS[i],
409                                 )
410                 else:
411                         #autorange
412                         if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
413                                 bounds = [common.get_min_max(samples) for samples in sampleses]
414                                 y_min = numpy.min([bound[0] for bound in bounds])
415                                 y_max = numpy.max([bound[1] for bound in bounds])
416                                 #adjust the y per div
417                                 y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
418                                 if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div)
419                                 #adjust the y offset
420                                 y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
421                                 if y_off != self[Y_OFF_KEY]: self.set_y_off(y_off)
422                                 self.autorange_ts = time.time()
423                         #plot each waveform
424                         for i, samples in enumerate(sampleses):
425                                 #number of samples to scale to the screen
426                                 num_samps = int(self[T_PER_DIV_KEY]*self[T_DIVS_KEY]*self.ext_controller[self.sample_rate_key])
427                                 #handle num samps out of bounds
428                                 if num_samps > len(samples):
429                                         self.set_t_per_div(
430                                                 common.get_clean_decr(self[T_PER_DIV_KEY]))
431                                 elif num_samps < 2:
432                                         self.set_t_per_div(
433                                                 common.get_clean_incr(self[T_PER_DIV_KEY]))
434                                         num_samps = 0
435                                 else:
436                                         #plot samples
437                                         self.plotter.set_waveform(
438                                                 channel='Ch%d'%(i+1),
439                                                 samples=samples[:num_samps],
440                                                 color_spec=CHANNEL_COLOR_SPECS[i],
441                                                 marker=self[MARKER_KEY],
442                                         )
443                         #turn XY channel off
444                         self.plotter.set_waveform(
445                                 channel='XY',
446                                 samples=[],
447                                 color_spec=CHANNEL_COLOR_SPECS[0],
448                         )
449                 #update the plotter
450                 self.plotter.update()
451
452         def update_grid(self, *args):
453                 """
454                 Update the grid to reflect the current settings:
455                 xy divisions, xy offset, xy mode setting
456                 """
457                 #grid parameters
458                 t_per_div = self[T_PER_DIV_KEY]
459                 x_per_div = self[X_PER_DIV_KEY]
460                 y_per_div = self[Y_PER_DIV_KEY]
461                 t_off = self[T_OFF_KEY]
462                 x_off = self[X_OFF_KEY]
463                 y_off = self[Y_OFF_KEY]
464                 t_divs = self[T_DIVS_KEY]
465                 x_divs = self[X_DIVS_KEY]
466                 y_divs = self[Y_DIVS_KEY]
467                 if self[SCOPE_XY_MODE_KEY]:
468                         #update the x axis
469                         self.plotter.set_x_label('Ch%d'%(self[SCOPE_X_CHANNEL_KEY]+1))
470                         self.plotter.set_x_grid(
471                                 -1*x_per_div*x_divs/2.0 + x_off,
472                                 x_per_div*x_divs/2.0 + x_off,
473                                 x_per_div,
474                         )
475                         #update the y axis
476                         self.plotter.set_y_label('Ch%d'%(self[SCOPE_Y_CHANNEL_KEY]+1))
477                         self.plotter.set_y_grid(
478                                 -1*y_per_div*y_divs/2.0 + y_off,
479                                 y_per_div*y_divs/2.0 + y_off,
480                                 y_per_div,
481                         )
482                 else:
483                         #update the t axis
484                         coeff, exp, prefix = common.get_si_components(t_per_div*t_divs + t_off)
485                         self.plotter.set_x_label('Time', prefix+'s')
486                         self.plotter.set_x_grid(
487                                 t_off,
488                                 t_per_div*t_divs + t_off,
489                                 t_per_div,
490                                 10**(-exp),
491                         )
492                         #update the y axis
493                         self.plotter.set_y_label('Counts')
494                         self.plotter.set_y_grid(
495                                 -1*y_per_div*y_divs/2.0 + y_off,
496                                 y_per_div*y_divs/2.0 + y_off,
497                                 y_per_div,
498                         )
499                 #redraw current sample
500                 self.handle_samples()