[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Commit-gnuradio] r8733 - gnuradio/branches/developers/jblum/gr-wxglgui/
From: |
jblum |
Subject: |
[Commit-gnuradio] r8733 - gnuradio/branches/developers/jblum/gr-wxglgui/src/python |
Date: |
Thu, 26 Jun 2008 18:20:22 -0600 (MDT) |
Author: jblum
Date: 2008-06-26 18:20:21 -0600 (Thu, 26 Jun 2008)
New Revision: 8733
Modified:
gnuradio/branches/developers/jblum/gr-wxglgui/src/python/fftsink.py
gnuradio/branches/developers/jblum/gr-wxglgui/src/python/plotter.py
Log:
almost functioning fft plot in ogl
Modified: gnuradio/branches/developers/jblum/gr-wxglgui/src/python/fftsink.py
===================================================================
--- gnuradio/branches/developers/jblum/gr-wxglgui/src/python/fftsink.py
2008-06-26 23:14:37 UTC (rev 8732)
+++ gnuradio/branches/developers/jblum/gr-wxglgui/src/python/fftsink.py
2008-06-27 00:20:21 UTC (rev 8733)
@@ -18,3 +18,397 @@
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
+
+##################################################
+# Imports
+##################################################
+from gnuradio import gr, window
+import plotter
+import wx
+import math
+import threading
+import numpy
+
+##################################################
+# Constants
+##################################################
+DEFAULT_FFT_RATE = gr.prefs().get_long('wxgui', 'fft_rate', 15)
+DIV_LEVELS = (1, 2, 5, 10, 20)
+
+##################################################
+# FFT window control panel
+##################################################
+class control_panel(wx.Panel):
+ """!
+ A control panel with wx widgits to control the plotter and fft block
chain.
+ """
+
+ class LabelText(wx.StaticText):
+ def __init__(self, window, label):
+ wx.StaticText.__init__(self, window, -1, label)
+ font = self.GetFont()
+ font.SetWeight(wx.FONTWEIGHT_BOLD)
+ font.SetUnderlined(True)
+ self.SetFont(font)
+
+ def __init__(self, parent):
+ """!
+ Create a new control panel.
+ @param parent the wx parent window
+ """
+ self.parent = parent
+ wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)
+ control_box = wx.BoxSizer(wx.VERTICAL)
+
+ #checkboxes for average and peak hold
+ control_box.AddStretchSpacer()
+ control_box.Add(self.LabelText(self, 'Options'), 0,
wx.ALIGN_CENTER)
+ self.average_check_box = wx.CheckBox(parent=self,
style=wx.CHK_2STATE, label="Average")
+ self.average_check_box.Bind(wx.EVT_CHECKBOX, parent.on_average)
+ control_box.Add(self.average_check_box, 0, wx.EXPAND)
+ self.peak_hold_check_box = wx.CheckBox(parent=self,
style=wx.CHK_2STATE, label="Peak Hold")
+ self.peak_hold_check_box.Bind(wx.EVT_CHECKBOX,
parent.on_peak_hold)
+ control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND)
+
+ #radio buttons for div size
+ control_box.AddStretchSpacer()
+ control_box.Add(self.LabelText(self, 'Set dB/div'), 0,
wx.ALIGN_CENTER)
+ radio_box = wx.BoxSizer(wx.VERTICAL)
+ self.radio_buttons = list()
+ for y_per_div in DIV_LEVELS:
+ radio_button = wx.RadioButton(self, -1, "%d
dB/div"%y_per_div)
+ radio_button.Bind(wx.EVT_RADIOBUTTON,
self.on_radio_button_change)
+ self.radio_buttons.append(radio_button)
+ radio_box.Add(radio_button, 0, wx.ALIGN_LEFT)
+ control_box.Add(radio_box, 0, wx.EXPAND)
+
+ #ref lvl buttons
+ control_box.AddStretchSpacer()
+ control_box.Add(self.LabelText(self, 'Adj Ref Lvl'), 0,
wx.ALIGN_CENTER)
+ control_box.AddSpacer(2)
+ button_box = wx.BoxSizer(wx.HORIZONTAL)
+ self.ref_plus_button = wx.Button(self, -1, '+',
style=wx.BU_EXACTFIT)
+ self.ref_plus_button.Bind(wx.EVT_BUTTON,
parent.on_incr_ref_level)
+ button_box.Add(self.ref_plus_button, 0, wx.ALIGN_CENTER)
+ self.ref_minus_button = wx.Button(self, -1, ' - ',
style=wx.BU_EXACTFIT)
+ self.ref_minus_button.Bind(wx.EVT_BUTTON,
parent.on_decr_ref_level)
+ button_box.Add(self.ref_minus_button, 0, wx.ALIGN_CENTER)
+ control_box.Add(button_box, 0, wx.ALIGN_CENTER)
+ control_box.AddStretchSpacer()
+ #set sizer
+ self.SetSizerAndFit(control_box)
+ #update
+ self.update()
+
+ def update(self):
+ """!
+ Read the state of the fft plot settings and update the control
panel.
+ """
+ #update checkboxes
+ self.average_check_box.SetValue(self.parent.average)
+ self.peak_hold_check_box.SetValue(self.parent.peak_hold)
+ #update radio buttons
+ try:
+ index = list(DIV_LEVELS).index(self.parent.y_per_div)
+ self.radio_buttons[index].SetValue(True)
+ except: pass
+
+ def on_radio_button_change(self, event):
+ selected_radio_button = filter(lambda rb: rb.GetValue(),
self.radio_buttons)[0]
+ index = self.radio_buttons.index(selected_radio_button)
+ self.parent.set_y_per_div(DIV_LEVELS[index])
+
+##################################################
+# FFT window with plotter and control panel
+##################################################
+class fft_window(wx.Panel):
+ def __init__(
+ self,
+ parent,
+ title,
+ y_per_div,
+ y_divs,
+ ref_level,
+ average,
+ peak_hold,
+ set_average,
+ ):
+ #ensure y_per_div
+ if y_per_div not in DIV_LEVELS: y_per_div = DIV_LEVELS[0]
+ #setup
+ self.y_per_div = y_per_div
+ self.y_divs = y_divs
+ self.ref_level = ref_level
+ self.average = average
+ self.peak_hold = peak_hold
+ self.set_average = set_average
+ #init panel and plot
+ wx.Panel.__init__(self, parent, -1)
+ self.plotter = plotter.grid_plotter(self, title,
'Frequency(Hz)', 'Amplitude(dB)', 35, 10, 40, 60)
+ #setup the box with plot and controls
+ self.control_panel = control_panel(self)
+ main_box = wx.BoxSizer(wx.HORIZONTAL)
+ main_box.Add(self.plotter, 1, wx.EXPAND)
+ main_box.Add(self.control_panel, 0, wx.EXPAND)
+ self.SetSizerAndFit(main_box)
+ #update
+ self.update()
+
+ def plot(self, samples):
+ self.plotter.set_waveform(
+ channel=1,
+ samples=samples,
+ offset=0.0,
+ color_spec=(0, 1, 0),
+ )
+ #update the plotter
+ self.plotter.update()
+
+ def update(self):
+ #update average
+ self.set_average(self.average)
+ #update peak hold
+ #TODO
+ #update y grid
+
self.plotter.set_y_grid(self.ref_level-self.y_per_div*self.y_divs,
self.ref_level, self.y_per_div)
+ #update control panel if non-gui changes occured
+ self.control_panel.update()
+ #update the plotter
+ self.plotter.update()
+
+ def on_average(self, event):
+ self.average = event.IsChecked()
+ self.update()
+
+ def on_peak_hold(self, event):
+ self.peak_hold = event.IsChecked()
+ self.update()
+
+ def on_incr_ref_level(self, event):
+ self.ref_level = self.ref_level + self.y_per_div
+ self.update()
+
+ def on_decr_ref_level(self, event):
+ self.ref_level = self.ref_level - self.y_per_div
+ self.update()
+
+ def set_y_per_div(self, y_per_div):
+ self.y_per_div = y_per_div
+ self.update()
+
+##################################################
+# FFT blocks chain: input -> message sink
+##################################################
+class fft_block(gr.hier_block2):
+ """!
+ Create an fft block chain, with real/complex input.
+ """
+
+ def __init__(self, item_size, sample_rate, fft_size, fft_rate,
ref_scale, avg_alpha):
+ """!
+ Create an fft block.
+ Write the samples to a message sink.
+ Provide access to the msg queue and setting the filter.
+ @param item_size the input size complex or float
+ @param sample_rate, fft_size, fft_rate, ref_scale, avg_alpha
the fft parameters
+ """
+ self._avg_alpha = avg_alpha
+ #init
+ gr.hier_block2.__init__(
+ self,
+ "fft_block",
+ gr.io_signature(1, 1, item_size),
+ gr.io_signature(0, 0, 0),
+ )
+ #create blocks
+ s2p = gr.stream_to_vector(item_size, fft_size)
+ one_in_n = gr.keep_one_in_n(item_size * fft_size, max(1,
int(sample_rate/fft_size/fft_rate)))
+ #create fft
+ fft_window = window.blackmanharris(fft_size)
+ fft = {
+ gr.sizeof_float: gr.fft_vfc,
+ gr.sizeof_gr_complex: gr.fft_vcc
+ }[item_size](fft_size, True, fft_window)
+ power = sum(map(lambda x: x*x, fft_window))
+ #filter fft, convert to dB
+ c2mag = gr.complex_to_mag(fft_size)
+ self._avg = gr.single_pole_iir_filter_ff(1.0, fft_size)
+ log = gr.nlog10_ff(
+ 20,
+ fft_size,
+ -10*math.log10(fft_size) # Adjust for number of bins
+ -10*math.log10(power/fft_size) # Adjust for windowing
loss
+ -20*math.log10(ref_scale/2) # Adjust for reference scale
+ )
+ #message sink
+ self.msgq = gr.msg_queue(2)
+ sink = gr.message_sink(gr.sizeof_float*fft_size, self.msgq,
True)
+ #connect
+ self.connect(self, s2p, one_in_n, fft, c2mag, self._avg, log,
sink)
+
+ def set_average(self, average):
+ """!
+ Set the averaging filter on/off.
+ @param average true to set averaging on
+ """
+ if average: self._avg.set_taps(self._avg_alpha)
+ else: self._avg.set_taps(1.0)
+
+##################################################
+# Input watcher for msg queue
+##################################################
+class input_watcher(threading.Thread):
+ def __init__ (self, msgq, handle_samples):
+ threading.Thread.__init__(self)
+ self.setDaemon(1)
+ self.msgq = msgq
+ self.handle_samples = handle_samples
+ self.keep_running = True
+ self.start()
+
+ def run(self):
+ while self.keep_running:
+ msg = self.msgq.delete_head() #blocking read of message
queue
+ itemsize = int(msg.arg1())
+ nitems = int(msg.arg2())
+
+ s = msg.to_string() #get the body of the msg as a string
+
+ # There may be more than one frame in the message.
+ # If so, we take only the last one
+ if nitems > 1:
+ start = itemsize * (nitems - 1)
+ s = s[start:start+itemsize]
+
+ samples = numpy.fromstring(s, numpy.float32)
+ self.handle_samples(samples)
+
+##################################################
+# FFT sink base block for real and complex types
+##################################################
+class _fft_sink_base(gr.hier_block2):
+ """!
+ An fft block with real/complex inputs.
+ The fft stream is written to a message sink.
+ This block provides a callback to set the average on/off.
+ """
+
+ def __init__(
+ self,
+ parent,
+ baseband_freq=0,
+ ref_scale=2.0,
+ y_per_div=10,
+ y_divs=8,
+ ref_level=50,
+ sample_rate=1,
+ fft_size=512,
+ fft_rate=DEFAULT_FFT_RATE,
+ average=False,
+ avg_alpha=None,
+ title='',
+ peak_hold=False,
+ ):
+ #ensure avg alpha
+ if avg_alpha is None: avg_alpha = 2.0/fft_rate
+ #init
+ gr.hier_block2.__init__(
+ self,
+ "fft_sink",
+ gr.io_signature(1, 1, self.item_size),
+ gr.io_signature(0, 0, 0),
+ )
+ copy = gr.kludge_copy(self.item_size)
+ #fft block
+ fft = fft_block(
+ item_size=self.item_size,
+ sample_rate=sample_rate,
+ fft_size=fft_size,
+ fft_rate=fft_rate,
+ ref_scale=ref_scale,
+ avg_alpha=avg_alpha,
+ )
+ #connect
+ self.connect(self, copy, fft)
+ #create window
+ self.win = fft_window(
+ parent=parent,
+ title=title,
+ y_per_div=y_per_div,
+ y_divs=y_divs,
+ ref_level=ref_level,
+ average=average,
+ peak_hold=peak_hold,
+ set_average=fft.set_average,
+ )
+ #setup x grid and setup the input watcher
+ if self.item_size == gr.sizeof_float:
+ self.win.plotter.set_x_grid(baseband_freq,
baseband_freq + sample_rate/2.0, int(sample_rate/16))
+ input_watcher(fft.msgq, self.win.plot)
+ else:
+ self.win.plotter.set_x_grid(baseband_freq -
sample_rate/2.0, baseband_freq + sample_rate/2.0, int(sample_rate/8))
+ input_watcher(fft.msgq, self._handle_complex_samples)
+
+ def _handle_complex_samples(self, samples):
+ """!
+ Reorder the complex fft samples so the negative bins come first
+ @param samples an array of fft samples
+ """
+ num_samples = len(samples)
+ samples = numpy.concatenate((samples[num_samples/2+1:],
samples[:num_samples/2]))
+ self.win.plot(samples)
+
+class fft_sink_f(_fft_sink_base): item_size = gr.sizeof_float
+class fft_sink_c(_fft_sink_base): item_size = gr.sizeof_gr_complex
+
+# ----------------------------------------------------------------
+# Standalone test app
+# ----------------------------------------------------------------
+
+class test_app_block (gr.top_block):
+ def __init__(self, frame, vbox):
+ gr.top_block.__init__(self)
+ fft_size = 256
+
+ # build our flow graph
+ input_rate = 20.48e3
+
+ # Generate a complex sinusoid
+ src1 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1)
+ #src1 = gr.sig_source_c (input_rate, gr.GR_CONST_WAVE, 5.75e3,
1)
+
+ # We add these throttle blocks so that this demo doesn't
+ # suck down all the CPU available. Normally you wouldn't use
these.
+ thr1 = gr.throttle(gr.sizeof_gr_complex, input_rate)
+
+ sink1 = fft_sink_c (frame, title="Complex Data",
fft_size=fft_size,
+ sample_rate=input_rate,
baseband_freq=100e3,
+ ref_level=0,
y_per_div=20, y_divs=10)
+ vbox.Add (sink1.win, 1, wx.EXPAND)
+
+ self.connect(src1, thr1, sink1)
+
+ src2 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1)
+ #src2 = gr.sig_source_f (input_rate, gr.GR_CONST_WAVE, 5.75e3,
1)
+ thr2 = gr.throttle(gr.sizeof_float, input_rate)
+ sink2 = fft_sink_f (frame, title="Real Data",
fft_size=fft_size*2,
+ sample_rate=input_rate,
baseband_freq=100e3,
+ ref_level=0,
y_per_div=20, y_divs=10)
+ vbox.Add (sink2.win, 1, wx.EXPAND)
+
+ self.connect(src2, thr2, sink2)
+ self.start()
+
+def main ():
+ app = wx.PySimpleApp()
+ frame = wx.Frame(None, -1, 'Demo', wx.DefaultPosition)
+ vbox = wx.BoxSizer(wx.VERTICAL)
+ test_app_block(frame, vbox)
+ frame.SetSizerAndFit(vbox)
+ frame.SetSize(wx.Size(700, 300))
+ frame.Show()
+ app.MainLoop()
+
+if __name__ == '__main__':
+ main ()
+
Modified: gnuradio/branches/developers/jblum/gr-wxglgui/src/python/plotter.py
===================================================================
--- gnuradio/branches/developers/jblum/gr-wxglgui/src/python/plotter.py
2008-06-26 23:14:37 UTC (rev 8732)
+++ gnuradio/branches/developers/jblum/gr-wxglgui/src/python/plotter.py
2008-06-27 00:20:21 UTC (rev 8733)
@@ -29,6 +29,7 @@
import gltext
import sys
import math
+import numpy
BACKGROUND_COLOR_SPEC = (1, 0.976, 1, 1) #creamy white
GRID_LINE_COLOR_SPEC = (0, 0, 0) #black
@@ -95,25 +96,26 @@
class grid_plotter(_plotter_base):
- def __init__(self, parent, title, units_x, units_y, padding_top,
padding_right, padding_bottom, padding_left):
-
- self.channels = dict()
-
+ def __init__(self, parent, title, x_units, y_units, padding_top,
padding_right, padding_bottom, padding_left):
+ """!
+ Create a new grid plotter.
+ """
+ self.channels = dict()
+ #store title and unit strings
self.title = title
- self.units_x = units_x
- self.units_y = units_y
-
+ self.x_units = x_units
+ self.y_units = y_units
+ #store padding
self.padding_top = padding_top
self.padding_right = padding_right
self.padding_bottom = padding_bottom
- self.padding_left = padding_left
-
- self.set_grid_x(-1, 1, 1)
- self.set_grid_y(-1, 1, 1)
-
+ self.padding_left = padding_left
+ #init the grid to some value
+ self.set_x_grid(-1, 1, 1)
+ self.set_y_grid(-1, 1, 1)
_plotter_base.__init__(self, parent)
- def set_grid_x(self, x_min, x_max, x_step):
+ def set_x_grid(self, x_min, x_max, x_step):
"""!
Set the x grid parameters.
@param x_min the left-most value
@@ -125,7 +127,7 @@
self.x_step = float(x_step)
self._changed = True
- def set_grid_y(self, y_min, y_max, y_step):
+ def set_y_grid(self, y_min, y_max, y_step):
"""!
Set the y grid parameters.
@param y_min the bottom-most value
@@ -148,6 +150,17 @@
self._draw_grid()
glEndList()
self._changed = False
+ self._draw_waveforms()
+ #draw the grid
+ glCallList(GRID_COMPILED_LIST_ID)
+ glFlush()
+ self.SwapBuffers()
+
+ def _draw_waveforms(self):
+ """!
+ Draw the waveforms for each channel.
+ Scale the waveform data to the grid using numpy (saves CPU).
+ """
##################################################
# Draw Waveforms
##################################################
@@ -155,15 +168,14 @@
glColor3f(*color_spec)
glBegin(GL_LINE_STRIP)
num_samps = len(samples)
- for i, samp in enumerate(samples):
- x =
(self.width-self.padding_left-self.padding_right)*i/float(num_samps-1) +
self.padding_left
- y =
(self.height-self.padding_top-self.padding_bottom)*(1 - (samp + offset -
self.y_min)/(self.y_max-self.y_min)) + self.padding_top
- glVertex3f(x, y, 0)
+ #use numpy to scale the waveform
+ x_scalar =
(self.width-self.padding_left-self.padding_right)
+ x_arr = x_scalar*numpy.arange(0,
num_samps)/float(num_samps-1) + self.padding_left
+ y_scalar =
(self.height-self.padding_top-self.padding_bottom)
+ y_arr = y_scalar*(1 - (numpy.array(samples) + offset -
self.y_min)/(self.y_max-self.y_min)) + self.padding_top
+ #draw the lines
+ for x, y in zip(x_arr, y_arr): glVertex3f(x, y, 0)
glEnd()
- #draw the grid
- glCallList(GRID_COMPILED_LIST_ID)
- glFlush()
- self.SwapBuffers()
def _draw_grid(self):
"""!
@@ -233,10 +245,10 @@
font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
font.SetWeight(wx.FONTWEIGHT_BOLD)
#draw x units
- txt = gltext.Text(self.units_x, font=font,
font_size=UNITS_TEXT_FONT_SIZE, centered=True)
+ txt = gltext.Text(self.x_units, font=font,
font_size=UNITS_TEXT_FONT_SIZE, centered=True)
txt.draw_text(wx.Point(self.width/2.0,
self.height-.25*self.padding_bottom))
#draw y units
- txt = gltext.Text(self.units_y, font=font,
font_size=UNITS_TEXT_FONT_SIZE, centered=True)
+ txt = gltext.Text(self.y_units, font=font,
font_size=UNITS_TEXT_FONT_SIZE, centered=True)
txt.draw_text(wx.Point(.25*self.padding_left, self.height/2.0),
rotation=90)
def _draw_tick_label(self, tick, coor):
@@ -309,67 +321,22 @@
"""
self.channels[channel] = samples, offset, color_spec
-from gnuradio import gr, window
-class test_block(gr.top_block):
- def __init__(self):
- gr.top_block.__init__(self)
-
- fft_size = 256
-
- input_rate = 55.48e3
-
- src = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2.32e3, 1)
- gr_noise_source_x = gr.noise_source_c(gr.GR_GAUSSIAN, 1, 42)
-
- gr_add_vxx = gr.add_vcc(1)
-
- thr = gr.throttle(gr.sizeof_gr_complex, input_rate)
-
- gr_stream_to_vector =
gr.stream_to_vector(gr.sizeof_gr_complex*1, fft_size)
- gr_fft_vxx = gr.fft_vcc(fft_size, True,
(window.blackmanharris(fft_size)))
- gr_complex_to_mag_fft = gr.complex_to_mag(fft_size)
-
- self.msgq = gr.msg_queue(1)
- sink = gr.message_sink(gr.sizeof_float * fft_size, self.msgq,
True)
-
- self.connect(src, gr_add_vxx, thr, gr_stream_to_vector,
gr_fft_vxx, gr_complex_to_mag_fft, sink)
- self.connect(gr_noise_source_x, (gr_add_vxx, 1))
-
-import threading
-import numpy
-class animate(threading.Thread):
- def __init__(self, plotter):
- self.plotter = plotter
- self.tb = test_block()
- threading.Thread.__init__(self)
- self.start()
-
- def run(self):
- self.plotter.set_grid_x(-1, 1, .2)
- self.plotter.set_grid_y(-30, 75, 30)
- self.tb.start()
- while 1:
- msg = self.tb.msgq.delete_head() # blocking read of
message queue
- itemsize = int(msg.arg1())
- nitems = int(msg.arg2())
- s = msg.to_string()
- if nitems > 1:
- start = itemsize * (nitems - 1)
- s = s[start:start+itemsize]
-
-
- arr = numpy.fromstring(s, numpy.float32)
- self.plotter.set_waveform(1, arr, -10, (0, 1, 0))
- self.plotter.set_waveform(2, arr, -20, (0, 0, 1))
- self.plotter.update()
-def main():
+if __name__ == '__main__':
app = wx.PySimpleApp()
- frame = wx.Frame(None, -1, 'Demo', wx.DefaultPosition, wx.Size(700,
300))
- plotter = grid_plotter(frame, 'Demo Grid', 'Frequency(Hz)',
'Amplitude(dB)', 35, 10, 40, 60)
- plotter.set_grid_x(-1, 1, .2)
- plotter.set_grid_y(-1, 1, .4)
- animate(plotter)
+ frame = wx.Frame(None, -1, 'Demo', wx.DefaultPosition)
+ vbox = wx.BoxSizer(wx.VERTICAL)
+
+ plotter = grid_plotter(frame, 'Demo Grid1', 'Frequency(Hz)',
'Amplitude(dB)', 35, 10, 40, 60)
+ plotter.set_x_grid(-1, 1, .2)
+ plotter.set_y_grid(-1, 1, .4)
+ vbox.Add(plotter, 1, wx.EXPAND)
+
+ plotter = grid_plotter(frame, 'Demo Grid2', 'Frequency(Hz)',
'Amplitude(dB)', 35, 10, 40, 60)
+ plotter.set_x_grid(-1, 1, .2)
+ plotter.set_y_grid(-1, 1, .4)
+ vbox.Add(plotter, 1, wx.EXPAND)
+
+ frame.SetSizerAndFit(vbox)
+ frame.SetSize(wx.Size(800, 600))
frame.Show()
app.MainLoop()
-
-if __name__ == '__main__': main()
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Commit-gnuradio] r8733 - gnuradio/branches/developers/jblum/gr-wxglgui/src/python,
jblum <=