From d9744799b7df6c09180778540bf55eb9dc84281b Mon Sep 17 00:00:00 2001 From: jcorgan Date: Fri, 20 Mar 2009 02:16:20 +0000 Subject: [PATCH] Merged r10463:10658 from jblum/gui_guts into trunk. Trunk passes distcheck. git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@10660 221aa14e-8319-0410-a670-987f0aec2ac5 --- gnuradio-core/src/lib/io/Makefile.am | 3 + gnuradio-core/src/lib/io/gr_histo_sink.i | 39 ++ gnuradio-core/src/lib/io/gr_histo_sink_f.cc | 169 +++++ gnuradio-core/src/lib/io/gr_histo_sink_f.h | 70 +++ gnuradio-core/src/lib/io/gr_oscope_guts.cc | 170 ++--- gnuradio-core/src/lib/io/gr_oscope_guts.h | 16 +- gnuradio-core/src/lib/io/gr_oscope_sink.i | 8 +- gnuradio-core/src/lib/io/gr_oscope_sink_f.cc | 10 +- gnuradio-core/src/lib/io/gr_oscope_sink_f.h | 5 +- gnuradio-core/src/lib/io/gr_oscope_sink_x.cc | 18 + gnuradio-core/src/lib/io/gr_oscope_sink_x.h | 3 + gnuradio-core/src/lib/io/gr_trigger_mode.h | 11 +- gnuradio-core/src/lib/io/io.i | 2 + gr-wxgui/src/python/Makefile.am | 2 + gr-wxgui/src/python/common.py | 226 ++++--- gr-wxgui/src/python/const_window.py | 80 +-- gr-wxgui/src/python/constants.py | 18 +- gr-wxgui/src/python/constsink_gl.py | 19 +- gr-wxgui/src/python/fft_window.py | 123 ++-- gr-wxgui/src/python/fftsink_gl.py | 15 +- gr-wxgui/src/python/fftsink_nongl.py | 2 +- gr-wxgui/src/python/histo_window.py | 154 +++++ gr-wxgui/src/python/histosink_gl.py | 110 ++++ gr-wxgui/src/python/number_window.py | 35 +- gr-wxgui/src/python/numbersink2.py | 44 +- gr-wxgui/src/python/plotter/Makefile.am | 5 +- gr-wxgui/src/python/plotter/__init__.py | 1 + gr-wxgui/src/python/plotter/bar_plotter.py | 144 +++++ .../src/python/plotter/channel_plotter.py | 127 ++-- gr-wxgui/src/python/plotter/common.py | 131 ++++ .../src/python/plotter/grid_plotter_base.py | 370 +++++++++++ gr-wxgui/src/python/plotter/plotter_base.py | 418 +++---------- .../src/python/plotter/waterfall_plotter.py | 107 ++-- gr-wxgui/src/python/pubsub.py | 106 ++-- gr-wxgui/src/python/scope_window.py | 589 ++++++++++-------- gr-wxgui/src/python/scopesink2.py | 4 +- gr-wxgui/src/python/scopesink_gl.py | 115 ++-- gr-wxgui/src/python/scopesink_nongl.py | 22 +- gr-wxgui/src/python/waterfall_window.py | 150 +++-- gr-wxgui/src/python/waterfallsink_gl.py | 14 +- grc/data/platforms/python/block_tree.xml | 1 + grc/data/platforms/python/blocks/Makefile.am | 1 + .../blocks/wxgui_constellationsink2.xml | 90 ++- .../python/blocks/wxgui_fftsink2.xml | 62 +- .../python/blocks/wxgui_histosink2.xml | 56 ++ .../python/blocks/wxgui_numbersink2.xml | 53 +- .../python/blocks/wxgui_scopesink2.xml | 68 +- grc/examples/simple/ber_simulation.grc | 186 +++--- .../trellis/interference_cancellation.grc | 392 +++++++----- .../grc_gnuradio/wxgui/callback_controls.py | 24 +- 50 files changed, 2901 insertions(+), 1687 deletions(-) create mode 100644 gnuradio-core/src/lib/io/gr_histo_sink.i create mode 100644 gnuradio-core/src/lib/io/gr_histo_sink_f.cc create mode 100644 gnuradio-core/src/lib/io/gr_histo_sink_f.h create mode 100644 gr-wxgui/src/python/histo_window.py create mode 100644 gr-wxgui/src/python/histosink_gl.py create mode 100644 gr-wxgui/src/python/plotter/bar_plotter.py create mode 100644 gr-wxgui/src/python/plotter/common.py create mode 100644 gr-wxgui/src/python/plotter/grid_plotter_base.py create mode 100644 grc/data/platforms/python/blocks/wxgui_histosink2.xml diff --git a/gnuradio-core/src/lib/io/Makefile.am b/gnuradio-core/src/lib/io/Makefile.am index c307be13..4583a033 100644 --- a/gnuradio-core/src/lib/io/Makefile.am +++ b/gnuradio-core/src/lib/io/Makefile.am @@ -33,6 +33,7 @@ libio_la_SOURCES = \ gr_file_source.cc \ gr_file_descriptor_sink.cc \ gr_file_descriptor_source.cc \ + gr_histo_sink_f.cc \ gr_message_sink.cc \ gr_message_source.cc \ gr_oscope_guts.cc \ @@ -64,6 +65,7 @@ grinclude_HEADERS = \ gr_file_source.h \ gr_file_descriptor_sink.h \ gr_file_descriptor_source.h \ + gr_histo_sink_f.h \ gr_message_sink.h \ gr_message_source.h \ gr_oscope_guts.h \ @@ -101,6 +103,7 @@ swiginclude_HEADERS = \ gr_file_source.i \ gr_file_descriptor_sink.i \ gr_file_descriptor_source.i \ + gr_histo_sink.i \ gr_message_sink.i \ gr_message_source.i \ gr_oscope_sink.i \ diff --git a/gnuradio-core/src/lib/io/gr_histo_sink.i b/gnuradio-core/src/lib/io/gr_histo_sink.i new file mode 100644 index 00000000..544d772f --- /dev/null +++ b/gnuradio-core/src/lib/io/gr_histo_sink.i @@ -0,0 +1,39 @@ +/* -*- c++ -*- */ +/* + * Copyright 2009 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * GNU Radio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + + +GR_SWIG_BLOCK_MAGIC(gr,histo_sink_f) + +gr_histo_sink_f_sptr gr_make_histo_sink_f (gr_msg_queue_sptr msgq); + +class gr_histo_sink_f : public gr_sync_block +{ +public: + ~gr_histo_sink_f (void); + + unsigned int get_frame_size(void); + unsigned int get_num_bins(void); + + void set_frame_size(unsigned int frame_size); + void set_num_bins(unsigned int num_bins); + +}; diff --git a/gnuradio-core/src/lib/io/gr_histo_sink_f.cc b/gnuradio-core/src/lib/io/gr_histo_sink_f.cc new file mode 100644 index 00000000..a923a7e4 --- /dev/null +++ b/gnuradio-core/src/lib/io/gr_histo_sink_f.cc @@ -0,0 +1,169 @@ +/* -*- c++ -*- */ +/* + * Copyright 2009 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * GNU Radio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +static float get_clean_num(float num){ + if (num == 0) return 0; + /* extract sign and exponent from num */ + int sign = (num < 0) ? -1 : 1; num = fabs(num); + float exponent = floor(log10(num)); + /* search for closest number with base 1, 2, 5, 10 */ + float closest_num = 10*pow(10, exponent); + if (fabs(num - 1*pow(10, exponent)) < fabs(num - closest_num)) + closest_num = 1*pow(10, exponent); + if (fabs(num - 2*pow(10, exponent)) < fabs(num - closest_num)) + closest_num = 2*pow(10, exponent); + if (fabs(num - 5*pow(10, exponent)) < fabs(num - closest_num)) + closest_num = 5*pow(10, exponent); + return sign*closest_num; +} + +gr_histo_sink_f_sptr +gr_make_histo_sink_f (gr_msg_queue_sptr msgq) +{ + return gr_histo_sink_f_sptr (new gr_histo_sink_f (msgq)); +} + +gr_histo_sink_f::gr_histo_sink_f (gr_msg_queue_sptr msgq) + : gr_sync_block ("histo_sink_f", gr_make_io_signature (1, 1, sizeof (float)), gr_make_io_signature (0, 0, 0)), + d_msgq (msgq), d_num_bins(11), d_frame_size(1000), d_sample_count(0), d_bins(NULL), d_samps(NULL) +{ + pthread_mutex_init(&d_mutex, 0); + //allocate arrays and clear + set_num_bins(d_num_bins); + set_frame_size(d_frame_size); +} + +gr_histo_sink_f::~gr_histo_sink_f (void) +{ + pthread_mutex_destroy(&d_mutex); + delete [] d_samps; + delete [] d_bins; +} + +int +gr_histo_sink_f::work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + const float *in = (const float *) input_items[0]; + pthread_mutex_lock(&d_mutex); + for (unsigned int i = 0; i < (unsigned int)noutput_items; i++){ + d_samps[d_sample_count] = in[i]; + d_sample_count++; + /* processed a frame? */ + if (d_sample_count == d_frame_size){ + send_frame(); + clear(); + } + } + pthread_mutex_unlock(&d_mutex); + return noutput_items; +} + +void +gr_histo_sink_f::send_frame(void){ + /* output queue full, drop the data */ + if (d_msgq->full_p()) return; + /* find the minimum and maximum */ + float minimum = d_samps[0]; + float maximum = d_samps[0]; + for (unsigned int i = 0; i < d_frame_size; i++){ + if (d_samps[i] < minimum) minimum = d_samps[i]; + if (d_samps[i] > maximum) maximum = d_samps[i]; + } + minimum = get_clean_num(minimum); + maximum = get_clean_num(maximum); + if (minimum == maximum || minimum > maximum) return; //useless data or screw up? + /* load the bins */ + int index; + float bin_width = (maximum - minimum)/(d_num_bins-1); + for (unsigned int i = 0; i < d_sample_count; i++){ + index = round((d_samps[i] - minimum)/bin_width); + /* ensure the index range in case a small floating point error is involed */ + if (index < 0) index = 0; + if (index >= (int)d_num_bins) index = d_num_bins-1; + d_bins[index]++; + } + /* Build a message to hold the output records */ + gr_message_sptr msg = gr_make_message(0, minimum, maximum, d_num_bins*sizeof(float)); + float *out = (float *)msg->msg(); // get pointer to raw message buffer + /* normalize the bins and put into message */ + for (unsigned int i = 0; i < d_num_bins; i++){ + out[i] = ((float)d_bins[i])/d_frame_size; + } + /* send the message */ + d_msgq->handle(msg); +} + +void +gr_histo_sink_f::clear(void){ + d_sample_count = 0; + /* zero the bins */ + for (unsigned int i = 0; i < d_num_bins; i++){ + d_bins[i] = 0; + } +} + +/************************************************** + * Getters + **************************************************/ +unsigned int +gr_histo_sink_f::get_frame_size(void){ + return d_frame_size; +} + +unsigned int +gr_histo_sink_f::get_num_bins(void){ + return d_num_bins; +} + +/************************************************** + * Setters + **************************************************/ +void +gr_histo_sink_f::set_frame_size(unsigned int frame_size){ + pthread_mutex_lock(&d_mutex); + d_frame_size = frame_size; + /* allocate a new sample array */ + delete [] d_samps; + d_samps = new float[d_frame_size]; + clear(); + pthread_mutex_unlock(&d_mutex); +} + +void +gr_histo_sink_f::set_num_bins(unsigned int num_bins){ + pthread_mutex_lock(&d_mutex); + d_num_bins = num_bins; + /* allocate a new bin array */ + delete [] d_bins; + d_bins = new unsigned int[d_num_bins]; + clear(); + pthread_mutex_unlock(&d_mutex); +} diff --git a/gnuradio-core/src/lib/io/gr_histo_sink_f.h b/gnuradio-core/src/lib/io/gr_histo_sink_f.h new file mode 100644 index 00000000..ae34d302 --- /dev/null +++ b/gnuradio-core/src/lib/io/gr_histo_sink_f.h @@ -0,0 +1,70 @@ +/* -*- c++ -*- */ +/* + * Copyright 2009 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * GNU Radio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef INCLUDED_GR_HISTO_SINK_F_H +#define INCLUDED_GR_HISTO_SINK_F_H + +#include +#include +#include + +class gr_histo_sink_f; +typedef boost::shared_ptr gr_histo_sink_f_sptr; + +gr_histo_sink_f_sptr gr_make_histo_sink_f (gr_msg_queue_sptr msgq); + +/*! + * \brief Histogram module. + * \ingroup sink + */ +class gr_histo_sink_f : public gr_sync_block +{ +private: + gr_msg_queue_sptr d_msgq; + unsigned int d_num_bins; + unsigned int d_frame_size; + unsigned int d_sample_count; + unsigned int *d_bins; + float *d_samps; + pthread_mutex_t d_mutex; + + friend gr_histo_sink_f_sptr gr_make_histo_sink_f (gr_msg_queue_sptr msgq); + gr_histo_sink_f (gr_msg_queue_sptr msgq); + void send_frame(void); + void clear(void); + +public: + ~gr_histo_sink_f (void); + + int work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + unsigned int get_frame_size(void); + unsigned int get_num_bins(void); + + void set_frame_size(unsigned int frame_size); + void set_num_bins(unsigned int num_bins); + +}; + +#endif /* INCLUDED_GR_HISTO_SINK_F_H */ diff --git a/gnuradio-core/src/lib/io/gr_oscope_guts.cc b/gnuradio-core/src/lib/io/gr_oscope_guts.cc index 2272aa0f..80f78240 100644 --- a/gnuradio-core/src/lib/io/gr_oscope_guts.cc +++ b/gnuradio-core/src/lib/io/gr_oscope_guts.cc @@ -31,30 +31,31 @@ #include #include -static const int OUTPUT_RECORD_SIZE = 2048; // must be power of 2 +static const int OUTPUT_RECORD_SIZE = 2048; // must be power of 2 static inline int -wrap_bi (int buffer_index) // wrap buffer index +wrap_bi (int buffer_index) // wrap buffer index { return buffer_index & (OUTPUT_RECORD_SIZE - 1); } static inline int -incr_bi (int buffer_index) // increment buffer index +incr_bi (int buffer_index) // increment buffer index { return wrap_bi (buffer_index + 1); } static inline int -decr_bi (int buffer_index) // decrement buffer index +decr_bi (int buffer_index) // decrement buffer index { return wrap_bi (buffer_index - 1); } -gr_oscope_guts::gr_oscope_guts (int nchannels, double sample_rate, gr_msg_queue_sptr msgq) - : d_nchannels (nchannels), +gr_oscope_guts::gr_oscope_guts (double sample_rate, gr_msg_queue_sptr msgq) + : d_nchannels (1), d_msgq (msgq), - d_trigger_mode (gr_TRIG_AUTO), + d_trigger_mode (gr_TRIG_MODE_AUTO), + d_trigger_slope (gr_TRIG_SLOPE_POS), d_trigger_channel (0), d_sample_rate (sample_rate), d_update_rate (20), @@ -65,19 +66,14 @@ gr_oscope_guts::gr_oscope_guts (int nchannels, double sample_rate, gr_msg_queue_ d_decimator_count_init (1), d_hold_off_count (0), d_hold_off_count_init (OUTPUT_RECORD_SIZE/2-1), + d_pre_trigger_count (0), d_post_trigger_count (0), - d_post_trigger_count_init (OUTPUT_RECORD_SIZE/2), - d_prev_sample (0) + d_post_trigger_count_init (OUTPUT_RECORD_SIZE/2) { - if (d_nchannels > MAX_CHANNELS){ - fprintf (stderr, "gr_oscope_guts: too many channels. MAX_CHANNELS = %d\n", MAX_CHANNELS); - throw std::runtime_error ("too many channels"); - } - for (int i = 0; i < MAX_CHANNELS; i++) d_buffer[i] = 0; - for (int i = 0; i < d_nchannels; i++){ + for (int i = 0; i < MAX_CHANNELS; i++){ d_buffer[i] = new float [OUTPUT_RECORD_SIZE]; for (int j = 0; j < OUTPUT_RECORD_SIZE; j++) d_buffer[i][j] = 0.0; @@ -109,10 +105,8 @@ gr_oscope_guts::process_sample (const float *channel_data) d_decimator_count = d_decimator_count_init; for (int i = 0; i < d_nchannels; i++) - d_buffer[i][d_obi] = channel_data[i]; // copy data into buffer + d_buffer[i][d_obi] = channel_data[i]; // copy data into buffer - int trigger = 0; - switch (d_state){ case HOLD_OFF: d_hold_off_count--; @@ -121,12 +115,8 @@ gr_oscope_guts::process_sample (const float *channel_data) break; case LOOK_FOR_TRIGGER: - trigger = found_trigger (d_buffer[d_trigger_channel][d_obi]); - if (trigger != 0){ + if (found_trigger ()) enter_post_trigger (); - if (trigger < 0) // previous sample was closer - d_post_trigger_count--; - } break; case POST_TRIGGER: @@ -158,8 +148,8 @@ gr_oscope_guts::enter_hold_off () void gr_oscope_guts::enter_look_for_trigger () { + d_pre_trigger_count = 0; d_state = LOOK_FOR_TRIGGER; - d_prev_sample = d_buffer[d_trigger_channel][d_obi]; } void @@ -167,48 +157,49 @@ gr_oscope_guts::enter_post_trigger () { d_state = POST_TRIGGER; d_post_trigger_count = d_post_trigger_count_init; + //ensure that the trigger offset is no more than than half a sample + if (d_trigger_off > .5) d_trigger_off -= 1; + else d_post_trigger_count--; } // ---------------------------------------------------------------- -// returns 0 if no trigger found. -// returns +1 if this sample is the trigger point -// returns -1 if the previous sample is the trigger point +// returns true if trigger found -int -gr_oscope_guts::found_trigger (float new_sample) +bool +gr_oscope_guts::found_trigger () { - float prev_sample = d_prev_sample; - d_prev_sample = new_sample; - bool trig; + float prev_sample = d_buffer[d_trigger_channel][decr_bi(d_obi)]; + float new_sample = d_buffer[d_trigger_channel][d_obi]; switch (d_trigger_mode){ - case gr_TRIG_AUTO: // always trigger - return +1; - - case gr_TRIG_POS_SLOPE: - trig = prev_sample < d_trigger_level && new_sample >= d_trigger_level; - if (trig){ - if (fabs (prev_sample - d_trigger_level) < fabs (new_sample - d_trigger_level)) - return -1; - else - return +1; - } - return 0; - - case gr_TRIG_NEG_SLOPE: - trig = prev_sample > d_trigger_level && new_sample <= d_trigger_level; - if (trig){ - if (fabs (prev_sample - d_trigger_level) < fabs (new_sample - d_trigger_level)) - return -1; - else - return +1; + case gr_TRIG_MODE_AUTO: //too many samples without a trigger + d_pre_trigger_count++; + if (d_pre_trigger_count > OUTPUT_RECORD_SIZE/2) return true; + + case gr_TRIG_MODE_NORM: //look for trigger + switch (d_trigger_slope){ + + case gr_TRIG_SLOPE_POS: //trigger point in pos slope? + if (new_sample < d_trigger_level || prev_sample >= d_trigger_level) return false; + break; + + case gr_TRIG_SLOPE_NEG: //trigger point in neg slope? + if (new_sample > d_trigger_level || prev_sample <= d_trigger_level) return false; + break; } - return 0; + + //calculate the trigger offset in % sample + d_trigger_off = (d_trigger_level - prev_sample)/(new_sample - prev_sample); + return true; + + case gr_TRIG_MODE_FREE: //free run mode, always trigger + d_trigger_off = 0; + return true; default: assert (0); - return 0; + return false; } } @@ -218,28 +209,30 @@ gr_oscope_guts::found_trigger (float new_sample) void gr_oscope_guts::write_output_records () { - // if the output queue if full, drop the data on the ground. + // if the output queue if full, drop the data like its hot. if (d_msgq->full_p()) return; - - // Build a message to hold the output records + // Build a message to hold the output records gr_message_sptr msg = - gr_make_message(0, // msg type - d_nchannels, // arg1 for other side - OUTPUT_RECORD_SIZE, // arg2 for other side - d_nchannels * OUTPUT_RECORD_SIZE * sizeof(float)); // sizeof payload + gr_make_message(0, // msg type + d_nchannels, // arg1 for other side + OUTPUT_RECORD_SIZE, // arg2 for other side + ((d_nchannels * OUTPUT_RECORD_SIZE) + 1) * sizeof(float)); // sizeof payload - float *out = (float *)msg->msg(); // get pointer to raw message buffer + float *out = (float *)msg->msg(); // get pointer to raw message buffer for (int ch = 0; ch < d_nchannels; ch++){ // note that d_obi + 1 points at the oldest sample in the buffer - for (int i = 0; i < OUTPUT_RECORD_SIZE; i++) + for (int i = 0; i < OUTPUT_RECORD_SIZE; i++){ out[i] = d_buffer[ch][wrap_bi(d_obi + 1 + i)]; - + } out += OUTPUT_RECORD_SIZE; } - - d_msgq->handle(msg); // send the msg + //Set the last sample as the trigger offset: + // The non gl scope sink will not look at this last sample. + // The gl scope sink will use this last sample as an offset. + out[0] = d_trigger_off; + d_msgq->handle(msg); // send the msg } // ---------------------------------------------------------------- @@ -291,15 +284,17 @@ gr_oscope_guts::set_trigger_channel (int channel) bool gr_oscope_guts::set_trigger_mode (gr_trigger_mode mode) { - switch (mode){ - case gr_TRIG_POS_SLOPE: - case gr_TRIG_NEG_SLOPE: - case gr_TRIG_AUTO: - d_trigger_mode = mode; - trigger_changed (); - return true; - } - return false; + d_trigger_mode = mode; + trigger_changed (); + return true; +} + +bool +gr_oscope_guts::set_trigger_slope (gr_trigger_slope slope) +{ + d_trigger_slope = slope; + trigger_changed (); + return true; } bool @@ -315,23 +310,30 @@ gr_oscope_guts::set_trigger_level_auto () { // find the level 1/2 way between the min and the max - float min_v = d_buffer[d_trigger_channel][0]; - float max_v = d_buffer[d_trigger_channel][0]; + float min_v = d_buffer[d_trigger_channel][0]; + float max_v = d_buffer[d_trigger_channel][0]; for (int i = 1; i < OUTPUT_RECORD_SIZE; i++){ min_v = std::min (min_v, d_buffer[d_trigger_channel][i]); max_v = std::max (max_v, d_buffer[d_trigger_channel][i]); } + return set_trigger_level((min_v + max_v) * 0.5); +} - d_trigger_level = (min_v + max_v) * 0.5; - trigger_changed (); - return true; +bool +gr_oscope_guts::set_num_channels(int nchannels) +{ + if (nchannels > 0 && nchannels <= MAX_CHANNELS){ + d_nchannels = nchannels; + return true; + } + return false; } + void gr_oscope_guts::trigger_changed () { - // d_prev_sample = d_buffer[d_trigger_channel][decr_bi(d_obi)]; enter_look_for_trigger (); } @@ -373,6 +375,12 @@ gr_oscope_guts::get_trigger_mode () const return d_trigger_mode; } +gr_trigger_slope +gr_oscope_guts::get_trigger_slope () const +{ + return d_trigger_slope; +} + double gr_oscope_guts::get_trigger_level () const { diff --git a/gnuradio-core/src/lib/io/gr_oscope_guts.h b/gnuradio-core/src/lib/io/gr_oscope_guts.h index ee3bbc31..f39db62f 100644 --- a/gnuradio-core/src/lib/io/gr_oscope_guts.h +++ b/gnuradio-core/src/lib/io/gr_oscope_guts.h @@ -41,13 +41,15 @@ */ class gr_oscope_guts { +public: + static const int MAX_CHANNELS = 8; private: - static const int MAX_CHANNELS = 16; enum scope_state { HOLD_OFF, LOOK_FOR_TRIGGER, POST_TRIGGER }; int d_nchannels; // how many channels gr_msg_queue_sptr d_msgq; // message queue we stuff output records into - gr_trigger_mode d_trigger_mode; + gr_trigger_mode d_trigger_mode; + gr_trigger_slope d_trigger_slope; int d_trigger_channel; // which channel to watch for trigger condition double d_sample_rate; // input sample rate in Hz double d_update_rate; // approx freq to produce an output record (Hz) @@ -61,9 +63,10 @@ private: int d_decimator_count_init; int d_hold_off_count; int d_hold_off_count_init; + int d_pre_trigger_count; int d_post_trigger_count; int d_post_trigger_count_init; - float d_prev_sample; // used for trigger checking + float d_trigger_off; //%sample trigger is off // NOT IMPLEMENTED gr_oscope_guts (const gr_oscope_guts &rhs); // no copy constructor @@ -71,7 +74,7 @@ private: void trigger_changed (); void update_rate_or_decimation_changed (); - int found_trigger (float sample); // returns -1, 0, +1 + bool found_trigger (); // returns true if found void write_output_records (); void enter_hold_off (); // called on state entry @@ -80,7 +83,7 @@ private: public: // CREATORS - gr_oscope_guts (int nchannels, double sample_rate, gr_msg_queue_sptr msgq); + gr_oscope_guts (double sample_rate, gr_msg_queue_sptr msgq); ~gr_oscope_guts (); // MANIPULATORS @@ -95,9 +98,11 @@ public: bool set_decimation_count (int decimation_count); bool set_trigger_channel (int channel); bool set_trigger_mode (gr_trigger_mode mode); + bool set_trigger_slope (gr_trigger_slope slope); bool set_trigger_level (double trigger_level); bool set_trigger_level_auto (); // set to 50% level bool set_sample_rate(double sample_rate); + bool set_num_channels(int nchannels); // ACCESSORS @@ -107,6 +112,7 @@ public: int get_decimation_count () const; int get_trigger_channel () const; gr_trigger_mode get_trigger_mode () const; + gr_trigger_slope get_trigger_slope () const; double get_trigger_level () const; // # of samples written to each output record. diff --git a/gnuradio-core/src/lib/io/gr_oscope_sink.i b/gnuradio-core/src/lib/io/gr_oscope_sink.i index 7802dac8..9d634193 100644 --- a/gnuradio-core/src/lib/io/gr_oscope_sink.i +++ b/gnuradio-core/src/lib/io/gr_oscope_sink.i @@ -20,11 +20,7 @@ * Boston, MA 02110-1301, USA. */ -enum gr_trigger_mode { - gr_TRIG_AUTO, // auto trigger (on anything) - gr_TRIG_POS_SLOPE, // trigger on positive slope across trigger level - gr_TRIG_NEG_SLOPE // trigger on negative slope across trigger level -}; +%include gr_trigger_mode.h // GR_SWIG_BLOCK_MAGIC(gr,oscope_sink_x) @@ -43,6 +39,7 @@ class gr_oscope_sink_x : public gr_sync_block bool set_decimation_count (int decimation_count); bool set_trigger_channel (int channel); bool set_trigger_mode (gr_trigger_mode mode); + bool set_trigger_slope (gr_trigger_slope slope); bool set_trigger_level (double trigger_level); bool set_trigger_level_auto (); // set to 50% level bool set_sample_rate(double sample_rate); @@ -54,6 +51,7 @@ class gr_oscope_sink_x : public gr_sync_block int get_decimation_count () const; int get_trigger_channel () const; gr_trigger_mode get_trigger_mode () const; + gr_trigger_slope get_trigger_slope () const; double get_trigger_level () const; // # of samples written to each output record. diff --git a/gnuradio-core/src/lib/io/gr_oscope_sink_f.cc b/gnuradio-core/src/lib/io/gr_oscope_sink_f.cc index 5e0e3066..cb401699 100644 --- a/gnuradio-core/src/lib/io/gr_oscope_sink_f.cc +++ b/gnuradio-core/src/lib/io/gr_oscope_sink_f.cc @@ -38,20 +38,18 @@ gr_make_oscope_sink_f (double sampling_rate, gr_msg_queue_sptr msgq) gr_oscope_sink_f::gr_oscope_sink_f (double sampling_rate, gr_msg_queue_sptr msgq) : gr_oscope_sink_x ("oscope_sink_f", - gr_make_io_signature (1, MAX_CHANNELS, sizeof (float)), + gr_make_io_signature (1, gr_oscope_guts::MAX_CHANNELS, sizeof (float)), sampling_rate), d_msgq(msgq) { + d_guts = new gr_oscope_guts (d_sampling_rate, d_msgq); } bool gr_oscope_sink_f::check_topology (int ninputs, int noutputs) { - delete d_guts; - d_guts = 0; - d_guts = new gr_oscope_guts (ninputs, d_sampling_rate, d_msgq); - return true; + return d_guts->set_num_channels(ninputs); } @@ -65,7 +63,7 @@ gr_oscope_sink_f::work (int noutput_items, gr_vector_void_star &output_items) { int ni = input_items.size (); - float tmp[MAX_CHANNELS]; + float tmp[gr_oscope_guts::MAX_CHANNELS]; for (int i = 0; i < noutput_items; i++){ diff --git a/gnuradio-core/src/lib/io/gr_oscope_sink_f.h b/gnuradio-core/src/lib/io/gr_oscope_sink_f.h index 620aac37..22d145c5 100644 --- a/gnuradio-core/src/lib/io/gr_oscope_sink_f.h +++ b/gnuradio-core/src/lib/io/gr_oscope_sink_f.h @@ -36,13 +36,10 @@ gr_oscope_sink_f_sptr gr_make_oscope_sink_f (double sampling_rate, gr_msg_queue_ * \brief Building block for python oscilloscope module. * \ingroup sink * - * Accepts 1 to 16 float streams. + * Accepts multiple float streams. */ class gr_oscope_sink_f : public gr_oscope_sink_x { -public: - static const int MAX_CHANNELS = 16; - private: friend gr_oscope_sink_f_sptr gr_make_oscope_sink_f (double sampling_rate, gr_msg_queue_sptr msgq); diff --git a/gnuradio-core/src/lib/io/gr_oscope_sink_x.cc b/gnuradio-core/src/lib/io/gr_oscope_sink_x.cc index f2c2d437..2bbd5746 100644 --- a/gnuradio-core/src/lib/io/gr_oscope_sink_x.cc +++ b/gnuradio-core/src/lib/io/gr_oscope_sink_x.cc @@ -68,6 +68,12 @@ gr_oscope_sink_x::set_trigger_mode (gr_trigger_mode mode) return d_guts->set_trigger_mode (mode); } +bool +gr_oscope_sink_x::set_trigger_slope (gr_trigger_slope slope) +{ + return d_guts->set_trigger_slope (slope); +} + bool gr_oscope_sink_x::set_trigger_level (double trigger_level) { @@ -87,6 +93,12 @@ gr_oscope_sink_x::set_sample_rate (double sample_rate) return d_guts->set_sample_rate (sample_rate); } +bool +gr_oscope_sink_x::set_num_channels (int nchannels) +{ + return d_guts->set_num_channels (nchannels); +} + // ACCESSORS int @@ -125,6 +137,12 @@ gr_oscope_sink_x::get_trigger_mode () const return d_guts->get_trigger_mode (); } +gr_trigger_slope +gr_oscope_sink_x::get_trigger_slope () const +{ + return d_guts->get_trigger_slope (); +} + double gr_oscope_sink_x::get_trigger_level () const { diff --git a/gnuradio-core/src/lib/io/gr_oscope_sink_x.h b/gnuradio-core/src/lib/io/gr_oscope_sink_x.h index 6dc0d633..408979eb 100644 --- a/gnuradio-core/src/lib/io/gr_oscope_sink_x.h +++ b/gnuradio-core/src/lib/io/gr_oscope_sink_x.h @@ -51,9 +51,11 @@ public: bool set_decimation_count (int decimation_count); bool set_trigger_channel (int channel); bool set_trigger_mode (gr_trigger_mode mode); + bool set_trigger_slope (gr_trigger_slope slope); bool set_trigger_level (double trigger_level); bool set_trigger_level_auto (); // set to 50% level bool set_sample_rate(double sample_rate); + bool set_num_channels (int nchannels); // ACCESSORS @@ -63,6 +65,7 @@ public: int get_decimation_count () const; int get_trigger_channel () const; gr_trigger_mode get_trigger_mode () const; + gr_trigger_slope get_trigger_slope () const; double get_trigger_level () const; // # of samples written to each output record. diff --git a/gnuradio-core/src/lib/io/gr_trigger_mode.h b/gnuradio-core/src/lib/io/gr_trigger_mode.h index 18d49ecf..63f6b1c9 100644 --- a/gnuradio-core/src/lib/io/gr_trigger_mode.h +++ b/gnuradio-core/src/lib/io/gr_trigger_mode.h @@ -24,9 +24,14 @@ #define INCLUDED_GR_TRIGGER_MODE_H enum gr_trigger_mode { - gr_TRIG_AUTO, // auto trigger (on anything) - gr_TRIG_POS_SLOPE, // trigger on positive slope across trigger level - gr_TRIG_NEG_SLOPE // trigger on negative slope across trigger level + gr_TRIG_MODE_FREE, + gr_TRIG_MODE_AUTO, + gr_TRIG_MODE_NORM, +}; + +enum gr_trigger_slope { + gr_TRIG_SLOPE_POS, + gr_TRIG_SLOPE_NEG, }; #endif /* INCLUDED_GR_TRIGGER_MODE_H */ diff --git a/gnuradio-core/src/lib/io/io.i b/gnuradio-core/src/lib/io/io.i index ed7feb49..3538942c 100644 --- a/gnuradio-core/src/lib/io/io.i +++ b/gnuradio-core/src/lib/io/io.i @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,7 @@ %include "gr_file_source.i" %include "gr_file_descriptor_sink.i" %include "gr_file_descriptor_source.i" +%include "gr_histo_sink.i" %include "microtune_xxxx_eval_board.i" %include "microtune_4702_eval_board.i" %include "microtune_4937_eval_board.i" diff --git a/gr-wxgui/src/python/Makefile.am b/gr-wxgui/src/python/Makefile.am index 58a9f575..45d75b60 100644 --- a/gr-wxgui/src/python/Makefile.am +++ b/gr-wxgui/src/python/Makefile.am @@ -42,6 +42,8 @@ ourpython_PYTHON = \ fftsink_gl.py \ fft_window.py \ gui.py \ + histosink_gl.py \ + histo_window.py \ numbersink2.py \ number_window.py \ plot.py \ diff --git a/gr-wxgui/src/python/common.py b/gr-wxgui/src/python/common.py index e7d08891..c84827eb 100644 --- a/gr-wxgui/src/python/common.py +++ b/gr-wxgui/src/python/common.py @@ -19,60 +19,86 @@ # Boston, MA 02110-1301, USA. # -import threading -import numpy -import math -import wx +#A macro to apply an index to a key +index_key = lambda key, i: "%s_%d"%(key, i+1) -class prop_setter(object): - def _register_set_prop(self, controller, control_key, *args): - def set_method(value): controller[control_key] = value - if args: set_method(args[0]) - setattr(self, 'set_%s'%control_key, set_method) +def _register_access_method(destination, controller, key): + """ + Helper function for register access methods. + This helper creates distinct set and get methods for each key + and adds them to the destination object. + """ + def set(value): controller[key] = value + setattr(destination, 'set_'+key, set) + def get(): return controller[key] + setattr(destination, 'get_'+key, get) -################################################## -# Custom Data Event -################################################## -EVT_DATA = wx.PyEventBinder(wx.NewEventType()) -class DataEvent(wx.PyEvent): - def __init__(self, data): - wx.PyEvent.__init__(self, wx.NewId(), EVT_DATA.typeId) - self.data = data +def register_access_methods(destination, controller): + """ + Register setter and getter functions in the destination object for all keys in the controller. + @param destination the object to get new setter and getter methods + @param controller the pubsub controller + """ + for key in controller.keys(): _register_access_method(destination, controller, key) ################################################## # Input Watcher Thread ################################################## +import threading + class input_watcher(threading.Thread): """ Input watcher thread runs forever. Read messages from the message queue. Forward messages to the message handler. """ - def __init__ (self, msgq, handle_msg): + def __init__ (self, msgq, controller, msg_key, arg1_key='', arg2_key=''): threading.Thread.__init__(self) self.setDaemon(1) self.msgq = msgq - self._handle_msg = handle_msg + self._controller = controller + self._msg_key = msg_key + self._arg1_key = arg1_key + self._arg2_key = arg2_key self.keep_running = True self.start() def run(self): - while self.keep_running: self._handle_msg(self.msgq.delete_head().to_string()) + while self.keep_running: + msg = self.msgq.delete_head() + if self._arg1_key: self._controller[self._arg1_key] = msg.arg1() + if self._arg2_key: self._controller[self._arg2_key] = msg.arg2() + self._controller[self._msg_key] = msg.to_string() ################################################## # WX Shared Classes ################################################## +import math +import wx + +EVT_DATA = wx.PyEventBinder(wx.NewEventType()) +class DataEvent(wx.PyEvent): + def __init__(self, data): + wx.PyEvent.__init__(self, wx.NewId(), EVT_DATA.typeId) + self.data = data + class LabelText(wx.StaticText): """ Label text to give the wx plots a uniform look. Get the default label text and set the font bold. """ def __init__(self, parent, label): - wx.StaticText.__init__(self, parent, -1, label) + wx.StaticText.__init__(self, parent, label=label) font = self.GetFont() font.SetWeight(wx.FONTWEIGHT_BOLD) self.SetFont(font) +class LabelBox(wx.BoxSizer): + def __init__(self, parent, label, widget): + wx.BoxSizer.__init__(self, wx.HORIZONTAL) + self.Add(wx.StaticText(parent, label=' %s '%label), 1, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT) + self.Add(widget, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) + class IncrDecrButtons(wx.BoxSizer): """ A horizontal box sizer with a increment and a decrement button. @@ -84,14 +110,14 @@ class IncrDecrButtons(wx.BoxSizer): @param on_decr the event handler for decrement """ wx.BoxSizer.__init__(self, wx.HORIZONTAL) - self._incr_button = wx.Button(parent, -1, '+', style=wx.BU_EXACTFIT) + self._incr_button = wx.Button(parent, label='+', style=wx.BU_EXACTFIT) self._incr_button.Bind(wx.EVT_BUTTON, on_incr) self.Add(self._incr_button, 0, wx.ALIGN_CENTER_VERTICAL) - self._decr_button = wx.Button(parent, -1, ' - ', style=wx.BU_EXACTFIT) + self._decr_button = wx.Button(parent, label=' - ', style=wx.BU_EXACTFIT) self._decr_button.Bind(wx.EVT_BUTTON, on_decr) self.Add(self._decr_button, 0, wx.ALIGN_CENTER_VERTICAL) - def Disable(self, disable=True): self.Enable(not disable) + def Disable(self): self.Enable(False) def Enable(self, enable=True): if enable: self._incr_button.Enable() @@ -104,7 +130,7 @@ class ToggleButtonController(wx.Button): def __init__(self, parent, controller, control_key, true_label, false_label): self._controller = controller self._control_key = control_key - wx.Button.__init__(self, parent, -1, '', style=wx.BU_EXACTFIT) + wx.Button.__init__(self, parent, style=wx.BU_EXACTFIT) self.Bind(wx.EVT_BUTTON, self._evt_button) controller.subscribe(control_key, lambda x: self.SetLabel(x and true_label or false_label)) @@ -122,30 +148,55 @@ class CheckBoxController(wx.CheckBox): def _evt_checkbox(self, e): self._controller[self._control_key] = bool(e.IsChecked()) +from gnuradio import eng_notation + +class TextBoxController(wx.TextCtrl): + def __init__(self, parent, controller, control_key, cast=float): + self._controller = controller + self._control_key = control_key + self._cast = cast + wx.TextCtrl.__init__(self, parent, style=wx.TE_PROCESS_ENTER) + self.Bind(wx.EVT_TEXT_ENTER, self._evt_enter) + controller.subscribe(control_key, lambda x: self.SetValue(eng_notation.num_to_str(x))) + + def _evt_enter(self, e): + try: self._controller[self._control_key] = self._cast(eng_notation.str_to_num(self.GetValue())) + except: self._controller[self._control_key] = self._controller[self._control_key] + class LogSliderController(wx.BoxSizer): """ Log slider controller with display label and slider. Gives logarithmic scaling to slider operation. """ - def __init__(self, parent, label, min_exp, max_exp, slider_steps, controller, control_key, formatter=lambda x: ': %.6f'%x): + def __init__(self, parent, prefix, min_exp, max_exp, slider_steps, controller, control_key, formatter=lambda x: ': %.6f'%x): + self._prefix = prefix + self._min_exp = min_exp + self._max_exp = max_exp + self._controller = controller + self._control_key = control_key + self._formatter = formatter wx.BoxSizer.__init__(self, wx.VERTICAL) - self._label = wx.StaticText(parent, -1, label + formatter(1/3.0)) + self._label = wx.StaticText(parent, label=prefix + formatter(1/3.0)) self.Add(self._label, 0, wx.EXPAND) - self._slider = wx.Slider(parent, -1, 0, 0, slider_steps, style=wx.SL_HORIZONTAL) + self._slider = wx.Slider(parent, minValue=0, maxValue=slider_steps, style=wx.SL_HORIZONTAL) self.Add(self._slider, 0, wx.EXPAND) - def _on_slider_event(event): - controller[control_key] = \ - 10**(float(max_exp-min_exp)*self._slider.GetValue()/slider_steps + min_exp) - self._slider.Bind(wx.EVT_SLIDER, _on_slider_event) - def _on_controller_set(value): - self._label.SetLabel(label + formatter(value)) - slider_value = slider_steps*(math.log10(value)-min_exp)/(max_exp-min_exp) - slider_value = min(max(0, slider_value), slider_steps) - if abs(slider_value - self._slider.GetValue()) > 1: - self._slider.SetValue(slider_value) - controller.subscribe(control_key, _on_controller_set) - - def Disable(self, disable=True): self.Enable(not disable) + self._slider.Bind(wx.EVT_SLIDER, self._on_slider_event) + controller.subscribe(control_key, self._on_controller_set) + + def _get_slope(self): + return float(self._max_exp-self._min_exp)/self._slider.GetMax() + + def _on_slider_event(self, e): + self._controller[self._control_key] = 10**(self._get_slope()*self._slider.GetValue() + self._min_exp) + + def _on_controller_set(self, value): + self._label.SetLabel(self._prefix + self._formatter(value)) + slider_value = (math.log10(value)-self._min_exp)/self._get_slope() + slider_value = min(max(self._slider.GetMin(), slider_value), self._slider.GetMax()) + if abs(slider_value - self._slider.GetValue()) > 1: + self._slider.SetValue(slider_value) + + def Disable(self): self.Enable(False) def Enable(self, enable=True): if enable: self._slider.Enable() @@ -154,45 +205,39 @@ class LogSliderController(wx.BoxSizer): self._slider.Disable() self._label.Disable() -class DropDownController(wx.BoxSizer): +class DropDownController(wx.Choice): """ Drop down controller with label and chooser. Srop down selection from a set of choices. """ - def __init__(self, parent, label, choices, controller, control_key, size=(-1, -1)): + def __init__(self, parent, choices, controller, control_key, size=(-1, -1)): """ @param parent the parent window - @param label the label for the drop down @param choices a list of tuples -> (label, value) @param controller the prop val controller @param control_key the prop key for this control """ - wx.BoxSizer.__init__(self, wx.HORIZONTAL) - self._label = wx.StaticText(parent, -1, ' %s '%label) - self.Add(self._label, 1, wx.ALIGN_CENTER_VERTICAL) - self._chooser = wx.Choice(parent, -1, choices=[c[0] for c in choices], size=size) - def _on_chooser_event(event): - controller[control_key] = choices[self._chooser.GetSelection()][1] - self._chooser.Bind(wx.EVT_CHOICE, _on_chooser_event) - self.Add(self._chooser, 0, wx.ALIGN_CENTER_VERTICAL) - def _on_controller_set(value): - #only set the chooser if the value is a possible choice - for i, choice in enumerate(choices): - if value == choice[1]: self._chooser.SetSelection(i) - controller.subscribe(control_key, _on_controller_set) - - def Disable(self, disable=True): self.Enable(not disable) - def Enable(self, enable=True): - if enable: - self._chooser.Enable() - self._label.Enable() - else: - self._chooser.Disable() - self._label.Disable() + self._controller = controller + self._control_key = control_key + self._choices = choices + wx.Choice.__init__(self, parent, choices=[c[0] for c in choices], size=size) + self.Bind(wx.EVT_CHOICE, self._on_chooser_event) + controller.subscribe(control_key, self._on_controller_set) + + def _on_chooser_event(self, e): + self._controller[self._control_key] = self._choices[self.GetSelection()][1] + + def _on_controller_set(self, value): + #only set the chooser if the value is a possible choice + for i, choice in enumerate(self._choices): + if value == choice[1]: self.SetSelection(i) ################################################## # Shared Functions ################################################## +import numpy +import math + def get_exp(num): """ Get the exponent of the number in base 10. @@ -209,8 +254,7 @@ def get_clean_num(num): @return the closest number """ if num == 0: return 0 - if num > 0: sign = 1 - else: sign = -1 + sign = num > 0 and 1 or -1 exp = get_exp(num) nums = numpy.array((1, 2, 5, 10))*(10**exp) return sign*nums[numpy.argmin(numpy.abs(nums - abs(num)))] @@ -263,49 +307,3 @@ def get_min_max(samples): min = mean - rms max = mean + rms return min, max - -def get_si_components(num): - """ - Get the SI units for the number. - Extract the coeff and exponent of the number. - The exponent will be a multiple of 3. - @param num the floating point number - @return the tuple coeff, exp, prefix - """ - exp = get_exp(num) - exp -= exp%3 - exp = min(max(exp, -24), 24) #bounds on SI table below - prefix = { - 24: 'Y', 21: 'Z', - 18: 'E', 15: 'P', - 12: 'T', 9: 'G', - 6: 'M', 3: 'K', - 0: '', - -3: 'm', -6: 'u', - -9: 'n', -12: 'p', - -15: 'f', -18: 'a', - -21: 'z', -24: 'y', - }[exp] - coeff = num/10**exp - return coeff, exp, prefix - -def label_format(num): - """ - Format a floating point number into a presentable string. - If the number has an small enough exponent, use regular decimal. - Otherwise, format the number with floating point notation. - Exponents are normalized to multiples of 3. - In the case where the exponent was found to be -3, - it is best to display this as a regular decimal, with a 0 to the left. - @param num the number to format - @return a label string - """ - coeff, exp, prefix = get_si_components(num) - if -3 <= exp < 3: return '%g'%num - return '%se%d'%('%.3g'%coeff, exp) - -if __name__ == '__main__': - import random - for i in range(-25, 25): - num = random.random()*10**i - print num, ':', get_si_components(num) diff --git a/gr-wxgui/src/python/const_window.py b/gr-wxgui/src/python/const_window.py index cb7236b4..8e0e61ac 100644 --- a/gr-wxgui/src/python/const_window.py +++ b/gr-wxgui/src/python/const_window.py @@ -62,32 +62,31 @@ class control_panel(wx.Panel): @param parent the wx parent window """ self.parent = parent - wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER) + wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) self.marker_index = 2 #begin control box - control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) - #marker - control_box.AddStretchSpacer() - self.marker_chooser = common.DropDownController(self, 'Marker', MARKER_TYPES, parent, MARKER_KEY) - control_box.Add(self.marker_chooser, 0, wx.EXPAND) #alpha control_box.AddStretchSpacer() - self.alpha_slider = common.LogSliderController( + alpha_slider = common.LogSliderController( self, 'Alpha', ALPHA_MIN_EXP, ALPHA_MAX_EXP, SLIDER_STEPS, - parent.ext_controller, parent.alpha_key, + parent, ALPHA_KEY, ) - control_box.Add(self.alpha_slider, 0, wx.EXPAND) + control_box.Add(alpha_slider, 0, wx.EXPAND) #gain_mu control_box.AddStretchSpacer() - self.gain_mu_slider = common.LogSliderController( + gain_mu_slider = common.LogSliderController( self, 'Gain Mu', GAIN_MU_MIN_EXP, GAIN_MU_MAX_EXP, SLIDER_STEPS, - parent.ext_controller, parent.gain_mu_key, + parent, GAIN_MU_KEY, ) - control_box.Add(self.gain_mu_slider, 0, wx.EXPAND) + control_box.Add(gain_mu_slider, 0, wx.EXPAND) + #marker + control_box.AddStretchSpacer() + marker_chooser = common.DropDownController(self, MARKER_TYPES, parent, MARKER_KEY) + control_box.Add(common.LabelBox(self, 'Marker', marker_chooser), 0, wx.EXPAND) #run/stop control_box.AddStretchSpacer() self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') @@ -98,7 +97,7 @@ class control_panel(wx.Panel): ################################################## # Constellation window with plotter and control panel ################################################## -class const_window(wx.Panel, pubsub.pubsub, common.prop_setter): +class const_window(wx.Panel, pubsub.pubsub): def __init__( self, parent, @@ -110,22 +109,27 @@ class const_window(wx.Panel, pubsub.pubsub, common.prop_setter): beta_key, gain_mu_key, gain_omega_key, + omega_key, + sample_rate_key, ): pubsub.pubsub.__init__(self) - #setup - self.ext_controller = controller - self.alpha_key = alpha_key - self.beta_key = beta_key - self.gain_mu_key = gain_mu_key - self.gain_omega_key = gain_omega_key + #proxy the keys + self.proxy(MSG_KEY, controller, msg_key) + self.proxy(ALPHA_KEY, controller, alpha_key) + self.proxy(BETA_KEY, controller, beta_key) + self.proxy(GAIN_MU_KEY, controller, gain_mu_key) + self.proxy(GAIN_OMEGA_KEY, controller, gain_omega_key) + self.proxy(OMEGA_KEY, controller, omega_key) + self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) #init panel and plot - wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) self.plotter.SetSize(wx.Size(*size)) self.plotter.set_title(title) self.plotter.set_x_label('Inphase') self.plotter.set_y_label('Quadrature') self.plotter.enable_point_label(True) + self.plotter.enable_grid_lines(True) #setup the box with plot and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) @@ -133,22 +137,21 @@ class const_window(wx.Panel, pubsub.pubsub, common.prop_setter): main_box.Add(self.control_panel, 0, wx.EXPAND) self.SetSizerAndFit(main_box) #alpha and gain mu 2nd orders - def set_beta(alpha): self.ext_controller[self.beta_key] = .25*alpha**2 - self.ext_controller.subscribe(self.alpha_key, set_beta) - def set_gain_omega(gain_mu): self.ext_controller[self.gain_omega_key] = .25*gain_mu**2 - self.ext_controller.subscribe(self.gain_mu_key, set_gain_omega) - #initial setup - self.ext_controller[self.alpha_key] = self.ext_controller[self.alpha_key] - self.ext_controller[self.gain_mu_key] = self.ext_controller[self.gain_mu_key] - self._register_set_prop(self, RUNNING_KEY, True) - self._register_set_prop(self, X_DIVS_KEY, 8) - self._register_set_prop(self, Y_DIVS_KEY, 8) - self._register_set_prop(self, MARKER_KEY, DEFAULT_MARKER_TYPE) + def set_beta(alpha): self[BETA_KEY] = .25*alpha**2 + self.subscribe(ALPHA_KEY, set_beta) + def set_gain_omega(gain_mu): self[GAIN_OMEGA_KEY] = .25*gain_mu**2 + self.subscribe(GAIN_MU_KEY, set_gain_omega) + #initialize values + self[ALPHA_KEY] = self[ALPHA_KEY] + self[GAIN_MU_KEY] = self[GAIN_MU_KEY] + self[RUNNING_KEY] = True + self[X_DIVS_KEY] = 8 + self[Y_DIVS_KEY] = 8 + self[MARKER_KEY] = DEFAULT_MARKER_TYPE #register events - self.ext_controller.subscribe(msg_key, self.handle_msg) - for key in ( - X_DIVS_KEY, Y_DIVS_KEY, - ): self.subscribe(key, self.update_grid) + self.subscribe(MSG_KEY, self.handle_msg) + self.subscribe(X_DIVS_KEY, self.update_grid) + self.subscribe(Y_DIVS_KEY, self.update_grid) #initial update self.update_grid() @@ -173,15 +176,12 @@ class const_window(wx.Panel, pubsub.pubsub, common.prop_setter): self.plotter.update() def update_grid(self): - #grid parameters - x_divs = self[X_DIVS_KEY] - y_divs = self[Y_DIVS_KEY] #update the x axis x_max = 2.0 - self.plotter.set_x_grid(-x_max, x_max, common.get_clean_num(2.0*x_max/x_divs)) + self.plotter.set_x_grid(-x_max, x_max, common.get_clean_num(2.0*x_max/self[X_DIVS_KEY])) #update the y axis y_max = 2.0 - self.plotter.set_y_grid(-y_max, y_max, common.get_clean_num(2.0*y_max/y_divs)) + self.plotter.set_y_grid(-y_max, y_max, common.get_clean_num(2.0*y_max/self[Y_DIVS_KEY])) #update plotter self.plotter.update() diff --git a/gr-wxgui/src/python/constants.py b/gr-wxgui/src/python/constants.py index 06c5a44f..a4ccdca6 100644 --- a/gr-wxgui/src/python/constants.py +++ b/gr-wxgui/src/python/constants.py @@ -36,6 +36,7 @@ FRAME_RATE_KEY = 'frame_rate' GAIN_MU_KEY = 'gain_mu' GAIN_OMEGA_KEY = 'gain_omega' MARKER_KEY = 'marker' +XY_MARKER_KEY = 'xy_marker' MSG_KEY = 'msg' NUM_LINES_KEY = 'num_lines' OMEGA_KEY = 'omega' @@ -43,15 +44,15 @@ PEAK_HOLD_KEY = 'peak_hold' REF_LEVEL_KEY = 'ref_level' RUNNING_KEY = 'running' SAMPLE_RATE_KEY = 'sample_rate' -SCOPE_TRIGGER_CHANNEL_KEY = 'scope_trigger_channel' -SCOPE_TRIGGER_LEVEL_KEY = 'scope_trigger_level' -SCOPE_TRIGGER_MODE_KEY = 'scope_trigger_mode' -SCOPE_X_CHANNEL_KEY = 'scope_x_channel' -SCOPE_Y_CHANNEL_KEY = 'scope_y_channel' -SCOPE_XY_MODE_KEY = 'scope_xy_mode' TRIGGER_CHANNEL_KEY = 'trigger_channel' TRIGGER_LEVEL_KEY = 'trigger_level' TRIGGER_MODE_KEY = 'trigger_mode' +TRIGGER_SLOPE_KEY = 'trigger_slope' +TRIGGER_SHOW_KEY = 'trigger_show' +XY_MODE_KEY = 'xy_mode' +X_CHANNEL_KEY = 'x_channel' +Y_CHANNEL_KEY = 'y_channel' +T_FRAC_OFF_KEY = 't_frac_off' T_DIVS_KEY = 't_divs' T_OFF_KEY = 't_off' T_PER_DIV_KEY = 't_per_div' @@ -61,4 +62,7 @@ X_PER_DIV_KEY = 'x_per_div' Y_DIVS_KEY = 'y_divs' Y_OFF_KEY = 'y_off' Y_PER_DIV_KEY = 'y_per_div' - +MAXIMUM_KEY = 'maximum' +MINIMUM_KEY = 'minimum' +NUM_BINS_KEY = 'num_bins' +FRAME_SIZE_KEY = 'frame_size' diff --git a/gr-wxgui/src/python/constsink_gl.py b/gr-wxgui/src/python/constsink_gl.py index 64666e46..b3a1625b 100644 --- a/gr-wxgui/src/python/constsink_gl.py +++ b/gr-wxgui/src/python/constsink_gl.py @@ -31,7 +31,7 @@ from constants import * ################################################## # Constellation sink block (wrapper for old wxgui) ################################################## -class const_sink_c(gr.hier_block2, common.prop_setter): +class const_sink_c(gr.hier_block2): """ A constellation block with a gui window. """ @@ -97,8 +97,7 @@ class const_sink_c(gr.hier_block2, common.prop_setter): #connect self.connect(self, self._costas, self._retime, agc, sd, sink) #controller - def setter(p, k, x): # lambdas can't have assignments :( - p[k] = x + def setter(p, k, x): p[k] = x self.controller = pubsub() self.controller.subscribe(ALPHA_KEY, self._costas.set_alpha) self.controller.publish(ALPHA_KEY, self._costas.alpha) @@ -116,7 +115,7 @@ class const_sink_c(gr.hier_block2, common.prop_setter): #initial update self.controller[SAMPLE_RATE_KEY] = sample_rate #start input watcher - common.input_watcher(msgq, lambda x: setter(self.controller, MSG_KEY, x)) + common.input_watcher(msgq, self.controller, MSG_KEY) #create window self.win = const_window.const_window( parent=parent, @@ -128,15 +127,9 @@ class const_sink_c(gr.hier_block2, common.prop_setter): beta_key=BETA_KEY, gain_mu_key=GAIN_MU_KEY, gain_omega_key=GAIN_OMEGA_KEY, + omega_key=OMEGA_KEY, + sample_rate_key=SAMPLE_RATE_KEY, ) - #register callbacks from window for external use - for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): - setattr(self, attr, getattr(self.win, attr)) - self._register_set_prop(self.controller, ALPHA_KEY) - self._register_set_prop(self.controller, BETA_KEY) - self._register_set_prop(self.controller, GAIN_MU_KEY) - self._register_set_prop(self.controller, OMEGA_KEY) - self._register_set_prop(self.controller, GAIN_OMEGA_KEY) - self._register_set_prop(self.controller, SAMPLE_RATE_KEY) + common.register_access_methods(self, self.win) diff --git a/gr-wxgui/src/python/fft_window.py b/gr-wxgui/src/python/fft_window.py index 6e54aec8..fdd5562d 100644 --- a/gr-wxgui/src/python/fft_window.py +++ b/gr-wxgui/src/python/fft_window.py @@ -39,8 +39,8 @@ AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0 DEFAULT_WIN_SIZE = (600, 300) DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'fft_rate', 30) DIV_LEVELS = (1, 2, 5, 10, 20) -FFT_PLOT_COLOR_SPEC = (0, 0, 1) -PEAK_VALS_COLOR_SPEC = (0, 1, 0) +FFT_PLOT_COLOR_SPEC = (0.3, 0.3, 1.0) +PEAK_VALS_COLOR_SPEC = (0.0, 0.8, 0.0) NO_PEAK_VALS = list() ################################################## @@ -57,31 +57,31 @@ class control_panel(wx.Panel): @param parent the wx parent window """ self.parent = parent - wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER) + wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) #checkboxes for average and peak hold control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) - self.average_check_box = common.CheckBoxController(self, 'Average', parent.ext_controller, parent.average_key) - control_box.Add(self.average_check_box, 0, wx.EXPAND) - self.peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY) - control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) + peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY) + control_box.Add(peak_hold_check_box, 0, wx.EXPAND) + average_check_box = common.CheckBoxController(self, 'Average', parent, AVERAGE_KEY) + control_box.Add(average_check_box, 0, wx.EXPAND) control_box.AddSpacer(2) - self.avg_alpha_slider = common.LogSliderController( + avg_alpha_slider = common.LogSliderController( self, 'Avg Alpha', AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, - parent.ext_controller, parent.avg_alpha_key, + parent, AVG_ALPHA_KEY, formatter=lambda x: ': %.4f'%x, ) - parent.ext_controller.subscribe(parent.average_key, self.avg_alpha_slider.Enable) - control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND) + parent.subscribe(AVERAGE_KEY, avg_alpha_slider.Enable) + control_box.Add(avg_alpha_slider, 0, wx.EXPAND) #radio buttons for div size control_box.AddStretchSpacer() control_box.Add(common.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 = wx.RadioButton(self, label="%d dB/div"%y_per_div) radio_button.Bind(wx.EVT_RADIOBUTTON, self._on_y_per_div) self.radio_buttons.append(radio_button) radio_box.Add(radio_button, 0, wx.ALIGN_LEFT) @@ -91,18 +91,23 @@ class control_panel(wx.Panel): control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Set Ref Level'), 0, wx.ALIGN_CENTER) control_box.AddSpacer(2) - self._ref_lvl_buttons = common.IncrDecrButtons(self, self._on_incr_ref_level, self._on_decr_ref_level) - control_box.Add(self._ref_lvl_buttons, 0, wx.ALIGN_CENTER) + _ref_lvl_buttons = common.IncrDecrButtons(self, self._on_incr_ref_level, self._on_decr_ref_level) + control_box.Add(_ref_lvl_buttons, 0, wx.ALIGN_CENTER) #autoscale control_box.AddStretchSpacer() - self.autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) - self.autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) - control_box.Add(self.autoscale_button, 0, wx.EXPAND) + autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) + autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) + control_box.Add(autoscale_button, 0, wx.EXPAND) #run/stop - self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(self.run_button, 0, wx.EXPAND) + run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') + control_box.Add(run_button, 0, wx.EXPAND) #set sizer self.SetSizerAndFit(control_box) + #mouse wheel event + def on_mouse_wheel(event): + if event.GetWheelRotation() < 0: self._on_incr_ref_level(event) + else: self._on_decr_ref_level(event) + parent.plotter.Bind(wx.EVT_MOUSEWHEEL, on_mouse_wheel) ################################################## # Event handlers @@ -117,16 +122,14 @@ class control_panel(wx.Panel): index = self.radio_buttons.index(selected_radio_button) self.parent[Y_PER_DIV_KEY] = DIV_LEVELS[index] def _on_incr_ref_level(self, event): - self.parent.set_ref_level( - self.parent[REF_LEVEL_KEY] + self.parent[Y_PER_DIV_KEY]) + self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] + self.parent[Y_PER_DIV_KEY] def _on_decr_ref_level(self, event): - self.parent.set_ref_level( - self.parent[REF_LEVEL_KEY] - self.parent[Y_PER_DIV_KEY]) + self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] - self.parent[Y_PER_DIV_KEY] ################################################## # FFT window with plotter and control panel ################################################## -class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): +class fft_window(wx.Panel, pubsub.pubsub): def __init__( self, parent, @@ -150,47 +153,48 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): if y_per_div not in DIV_LEVELS: y_per_div = DIV_LEVELS[0] #setup self.samples = list() - self.ext_controller = controller self.real = real self.fft_size = fft_size - self.sample_rate_key = sample_rate_key - self.average_key = average_key - self.avg_alpha_key = avg_alpha_key self._reset_peak_vals() + #proxy the keys + self.proxy(MSG_KEY, controller, msg_key) + self.proxy(AVERAGE_KEY, controller, average_key) + self.proxy(AVG_ALPHA_KEY, controller, avg_alpha_key) + self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) #init panel and plot - wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) self.plotter.SetSize(wx.Size(*size)) self.plotter.set_title(title) + self.plotter.enable_legend(True) self.plotter.enable_point_label(True) + self.plotter.enable_grid_lines(True) #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) - #initial setup - self.ext_controller[self.average_key] = self.ext_controller[self.average_key] - self.ext_controller[self.avg_alpha_key] = self.ext_controller[self.avg_alpha_key] - self._register_set_prop(self, PEAK_HOLD_KEY, peak_hold) - self._register_set_prop(self, Y_PER_DIV_KEY, y_per_div) - self._register_set_prop(self, Y_DIVS_KEY, y_divs) - self._register_set_prop(self, X_DIVS_KEY, 8) #approximate - self._register_set_prop(self, REF_LEVEL_KEY, ref_level) - self._register_set_prop(self, BASEBAND_FREQ_KEY, baseband_freq) - self._register_set_prop(self, RUNNING_KEY, True) + #initialize values + self[AVERAGE_KEY] = self[AVERAGE_KEY] + self[AVG_ALPHA_KEY] = self[AVG_ALPHA_KEY] + self[PEAK_HOLD_KEY] = peak_hold + self[Y_PER_DIV_KEY] = y_per_div + self[Y_DIVS_KEY] = y_divs + self[X_DIVS_KEY] = 8 #approximate + self[REF_LEVEL_KEY] = ref_level + self[BASEBAND_FREQ_KEY] = baseband_freq + self[RUNNING_KEY] = True #register events - self.subscribe(PEAK_HOLD_KEY, self.plotter.enable_legend) - self.ext_controller.subscribe(AVERAGE_KEY, lambda x: self._reset_peak_vals()) - self.ext_controller.subscribe(msg_key, self.handle_msg) - self.ext_controller.subscribe(self.sample_rate_key, self.update_grid) + self.subscribe(AVERAGE_KEY, lambda x: self._reset_peak_vals()) + self.subscribe(MSG_KEY, self.handle_msg) + self.subscribe(SAMPLE_RATE_KEY, self.update_grid) for key in ( BASEBAND_FREQ_KEY, Y_PER_DIV_KEY, X_DIVS_KEY, Y_DIVS_KEY, REF_LEVEL_KEY, ): self.subscribe(key, self.update_grid) #initial update - self.plotter.enable_legend(self[PEAK_HOLD_KEY]) self.update_grid() def autoscale(self, *args): @@ -207,9 +211,9 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): noise_floor -= abs(noise_floor)*.5 peak_level += abs(peak_level)*.1 #set the reference level to a multiple of y divs - self.set_ref_level(self[Y_DIVS_KEY]*math.ceil(peak_level/self[Y_DIVS_KEY])) + self[REF_LEVEL_KEY] = self[Y_DIVS_KEY]*math.ceil(peak_level/self[Y_DIVS_KEY]) #set the range to a clean number of the dynamic range - self.set_y_per_div(common.get_clean_num((peak_level - noise_floor)/self[Y_DIVS_KEY])) + self[Y_PER_DIV_KEY] = common.get_clean_num((peak_level - noise_floor)/self[Y_DIVS_KEY]) def _reset_peak_vals(self): self.peak_vals = NO_PEAK_VALS @@ -234,19 +238,21 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): if self[PEAK_HOLD_KEY]: if len(self.peak_vals) != len(samples): self.peak_vals = samples self.peak_vals = numpy.maximum(samples, self.peak_vals) - else: self._reset_peak_vals() + #plot the peak hold + self.plotter.set_waveform( + channel='Peak', + samples=self.peak_vals, + color_spec=PEAK_VALS_COLOR_SPEC, + ) + else: + self._reset_peak_vals() + self.plotter.clear_waveform(channel='Peak') #plot the fft self.plotter.set_waveform( channel='FFT', samples=samples, color_spec=FFT_PLOT_COLOR_SPEC, ) - #plot the peak hold - self.plotter.set_waveform( - channel='Peak', - samples=self.peak_vals, - color_spec=PEAK_VALS_COLOR_SPEC, - ) #update the plotter self.plotter.update() @@ -259,7 +265,7 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): The y axis depends on y per div, y divs, and ref level. """ #grid parameters - sample_rate = self.ext_controller[self.sample_rate_key] + sample_rate = self[SAMPLE_RATE_KEY] baseband_freq = self[BASEBAND_FREQ_KEY] y_per_div = self[Y_PER_DIV_KEY] y_divs = self[Y_DIVS_KEY] @@ -269,24 +275,21 @@ class fft_window(wx.Panel, pubsub.pubsub, common.prop_setter): if self.real: x_width = sample_rate/2.0 else: x_width = sample_rate/1.0 x_per_div = common.get_clean_num(x_width/x_divs) - coeff, exp, prefix = common.get_si_components(abs(baseband_freq) + abs(sample_rate/2.0)) #update the x grid if self.real: self.plotter.set_x_grid( baseband_freq, baseband_freq + sample_rate/2.0, - x_per_div, - 10**(-exp), + x_per_div, True, ) else: self.plotter.set_x_grid( baseband_freq - sample_rate/2.0, baseband_freq + sample_rate/2.0, - x_per_div, - 10**(-exp), + x_per_div, True, ) #update x units - self.plotter.set_x_label('Frequency', prefix+'Hz') + self.plotter.set_x_label('Frequency', 'Hz') #update y grid self.plotter.set_y_grid(ref_level-y_per_div*y_divs, ref_level, y_per_div) #update y units diff --git a/gr-wxgui/src/python/fftsink_gl.py b/gr-wxgui/src/python/fftsink_gl.py index 0c6f38dc..30ebd3fd 100644 --- a/gr-wxgui/src/python/fftsink_gl.py +++ b/gr-wxgui/src/python/fftsink_gl.py @@ -31,7 +31,7 @@ from constants import * ################################################## # FFT sink block (wrapper for old wxgui) ################################################## -class _fft_sink_base(gr.hier_block2, common.prop_setter): +class _fft_sink_base(gr.hier_block2): """ An fft block with real/complex inputs and a gui window. """ @@ -85,9 +85,7 @@ class _fft_sink_base(gr.hier_block2, common.prop_setter): self.controller.subscribe(SAMPLE_RATE_KEY, fft.set_sample_rate) self.controller.publish(SAMPLE_RATE_KEY, fft.sample_rate) #start input watcher - def setter(p, k, x): # lambdas can't have assignments :( - p[k] = x - common.input_watcher(msgq, lambda x: setter(self.controller, MSG_KEY, x)) + common.input_watcher(msgq, self.controller, MSG_KEY) #create window self.win = fft_window.fft_window( parent=parent, @@ -106,12 +104,9 @@ class _fft_sink_base(gr.hier_block2, common.prop_setter): peak_hold=peak_hold, msg_key=MSG_KEY, ) - #register callbacks from window for external use - for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): - setattr(self, attr, getattr(self.win, attr)) - self._register_set_prop(self.controller, SAMPLE_RATE_KEY) - self._register_set_prop(self.controller, AVERAGE_KEY) - self._register_set_prop(self.controller, AVG_ALPHA_KEY) + common.register_access_methods(self, self.win) + setattr(self.win, 'set_baseband_freq', getattr(self, 'set_baseband_freq')) #BACKWARDS + setattr(self.win, 'set_peak_hold', getattr(self, 'set_peak_hold')) #BACKWARDS class fft_sink_f(_fft_sink_base): _fft_chain = blks2.logpwrfft_f diff --git a/gr-wxgui/src/python/fftsink_nongl.py b/gr-wxgui/src/python/fftsink_nongl.py index d455ddc2..a1033e81 100644 --- a/gr-wxgui/src/python/fftsink_nongl.py +++ b/gr-wxgui/src/python/fftsink_nongl.py @@ -319,7 +319,7 @@ class fft_window (wx.Panel): # self.SetBackgroundColour ('black') self.build_popup_menu() - self.set_baseband_freq(0.0) + self.set_baseband_freq(self.fftsink.baseband_freq) EVT_DATA_EVENT (self, self.set_data) wx.EVT_CLOSE (self, self.on_close_window) diff --git a/gr-wxgui/src/python/histo_window.py b/gr-wxgui/src/python/histo_window.py new file mode 100644 index 00000000..dce52ff9 --- /dev/null +++ b/gr-wxgui/src/python/histo_window.py @@ -0,0 +1,154 @@ +# +# Copyright 2009 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +################################################## +# Imports +################################################## +import plotter +import common +import wx +import numpy +import math +import pubsub +from constants import * +from gnuradio import gr #for gr.prefs + +################################################## +# Constants +################################################## +DEFAULT_WIN_SIZE = (600, 300) + +################################################## +# histo window control panel +################################################## +class control_panel(wx.Panel): + """ + A control panel with wx widgits to control the plotter and histo sink. + """ + + def __init__(self, parent): + """ + Create a new control panel. + @param parent the wx parent window + """ + self.parent = parent + wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) + control_box = wx.BoxSizer(wx.VERTICAL) + SIZE = (100, -1) + control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) + control_box.AddStretchSpacer() + #num bins + def num_bins_cast(num): + num = int(num) + assert num > 1 + return num + num_bins_ctrl = common.TextBoxController(self, parent, NUM_BINS_KEY, cast=num_bins_cast) + control_box.Add(common.LabelBox(self, ' Num Bins ', num_bins_ctrl), 0, wx.EXPAND) + control_box.AddStretchSpacer() + #frame size + frame_size_ctrl = common.TextBoxController(self, parent, FRAME_SIZE_KEY, cast=num_bins_cast) + control_box.Add(common.LabelBox(self, ' Frame Size ', frame_size_ctrl), 0, wx.EXPAND) + control_box.AddStretchSpacer() + #run/stop + self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') + control_box.Add(self.run_button, 0, wx.EXPAND) + #set sizer + self.SetSizerAndFit(control_box) + +################################################## +# histo window with plotter and control panel +################################################## +class histo_window(wx.Panel, pubsub.pubsub): + def __init__( + self, + parent, + controller, + size, + title, + maximum_key, + minimum_key, + num_bins_key, + frame_size_key, + msg_key, + ): + pubsub.pubsub.__init__(self) + #setup + self.samples = list() + #proxy the keys + self.proxy(MAXIMUM_KEY, controller, maximum_key) + self.proxy(MINIMUM_KEY, controller, minimum_key) + self.proxy(NUM_BINS_KEY, controller, num_bins_key) + self.proxy(FRAME_SIZE_KEY, controller, frame_size_key) + self.proxy(MSG_KEY, controller, msg_key) + #init panel and plot + wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) + self.plotter = plotter.bar_plotter(self) + self.plotter.SetSize(wx.Size(*size)) + self.plotter.set_title(title) + self.plotter.enable_point_label(True) + self.plotter.enable_grid_lines(False) + #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) + #initialize values + self[NUM_BINS_KEY] = self[NUM_BINS_KEY] + self[FRAME_SIZE_KEY] = self[FRAME_SIZE_KEY] + self[RUNNING_KEY] = True + self[X_DIVS_KEY] = 8 + self[Y_DIVS_KEY] = 4 + #register events + self.subscribe(MSG_KEY, self.handle_msg) + self.subscribe(X_DIVS_KEY, self.update_grid) + self.subscribe(Y_DIVS_KEY, self.update_grid) + + def handle_msg(self, msg): + """ + Handle the message from the fft sink message queue. + @param msg the frame as a character array + """ + if not self[RUNNING_KEY]: return + #convert to floating point numbers + self.samples = 100*numpy.fromstring(msg, numpy.float32)[:self[NUM_BINS_KEY]] #only take first frame + self.plotter.set_bars( + bars=self.samples, + bar_width=0.6, + color_spec=(0, 0, 1), + ) + self.update_grid() + + def update_grid(self): + if not len(self.samples): return + #calculate the maximum y value + y_off = math.ceil(numpy.max(self.samples)) + y_off = min(max(y_off, 1.0), 100.0) #between 1% and 100% + #update the x grid + self.plotter.set_x_grid( + self[MINIMUM_KEY], self[MAXIMUM_KEY], + common.get_clean_num((self[MAXIMUM_KEY] - self[MINIMUM_KEY])/self[X_DIVS_KEY]), + ) + self.plotter.set_x_label('Counts') + #update the y grid + self.plotter.set_y_grid(0, y_off, y_off/self[Y_DIVS_KEY]) + self.plotter.set_y_label('Frequency', '%') + self.plotter.update() diff --git a/gr-wxgui/src/python/histosink_gl.py b/gr-wxgui/src/python/histosink_gl.py new file mode 100644 index 00000000..db6606e4 --- /dev/null +++ b/gr-wxgui/src/python/histosink_gl.py @@ -0,0 +1,110 @@ +# +# Copyright 2009 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +################################################## +# Imports +################################################## +import histo_window +import common +from gnuradio import gr, blks2 +from pubsub import pubsub +from constants import * + +################################################## +# histo sink block (wrapper for old wxgui) +################################################## +class histo_sink_f(gr.hier_block2): + """ + A histogram block and a gui window. + """ + + def __init__( + self, + parent, + size=histo_window.DEFAULT_WIN_SIZE, + title='', + num_bins=11, + frame_size=1000, + ): + #init + gr.hier_block2.__init__( + self, + "histo_sink", + gr.io_signature(1, 1, gr.sizeof_float), + gr.io_signature(0, 0, 0), + ) + #blocks + msgq = gr.msg_queue(2) + histo = gr.histo_sink_f(msgq) + histo.set_num_bins(num_bins) + histo.set_frame_size(frame_size) + #connect + self.connect(self, histo) + #controller + self.controller = pubsub() + self.controller.subscribe(NUM_BINS_KEY, histo.set_num_bins) + self.controller.publish(NUM_BINS_KEY, histo.get_num_bins) + self.controller.subscribe(FRAME_SIZE_KEY, histo.set_frame_size) + self.controller.publish(FRAME_SIZE_KEY, histo.get_frame_size) + #start input watcher + common.input_watcher(msgq, self.controller, MSG_KEY, arg1_key=MINIMUM_KEY, arg2_key=MAXIMUM_KEY) + #create window + self.win = histo_window.histo_window( + parent=parent, + controller=self.controller, + size=size, + title=title, + maximum_key=MAXIMUM_KEY, + minimum_key=MINIMUM_KEY, + num_bins_key=NUM_BINS_KEY, + frame_size_key=FRAME_SIZE_KEY, + msg_key=MSG_KEY, + ) + common.register_access_methods(self, self.win) + +# ---------------------------------------------------------------- +# Standalone test app +# ---------------------------------------------------------------- + +import wx +from gnuradio.wxgui import stdgui2 + +class test_app_block (stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv) + + # build our flow graph + input_rate = 20.48e3 + + 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 = histo_sink_f (panel, title="Data", num_bins=31, frame_size=1000) + vbox.Add (sink2.win, 1, wx.EXPAND) + + self.connect(src2, thr2, sink2) + +def main (): + app = stdgui2.stdapp (test_app_block, "Histo Sink Test App") + app.MainLoop () + +if __name__ == '__main__': + main () diff --git a/gr-wxgui/src/python/number_window.py b/gr-wxgui/src/python/number_window.py index e766c68c..f12a1824 100644 --- a/gr-wxgui/src/python/number_window.py +++ b/gr-wxgui/src/python/number_window.py @@ -53,23 +53,23 @@ class control_panel(wx.Panel): @param parent the wx parent window """ self.parent = parent - wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER) + wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) #checkboxes for average and peak hold control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) - self.average_check_box = common.CheckBoxController(self, 'Average', parent.ext_controller, parent.average_key) - control_box.Add(self.average_check_box, 0, wx.EXPAND) self.peak_hold_check_box = common.CheckBoxController(self, 'Peak Hold', parent, PEAK_HOLD_KEY) control_box.Add(self.peak_hold_check_box, 0, wx.EXPAND) + self.average_check_box = common.CheckBoxController(self, 'Average', parent, AVERAGE_KEY) + control_box.Add(self.average_check_box, 0, wx.EXPAND) control_box.AddSpacer(2) self.avg_alpha_slider = common.LogSliderController( self, 'Avg Alpha', AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, - parent.ext_controller, parent.avg_alpha_key, + parent, AVG_ALPHA_KEY, formatter=lambda x: ': %.4f'%x, ) - parent.ext_controller.subscribe(parent.average_key, self.avg_alpha_slider.Enable) + parent.subscribe(AVERAGE_KEY, self.avg_alpha_slider.Enable) control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND) #run/stop control_box.AddStretchSpacer() @@ -81,7 +81,7 @@ class control_panel(wx.Panel): ################################################## # Numbersink window with label and gauges ################################################## -class number_window(wx.Panel, pubsub.pubsub, common.prop_setter): +class number_window(wx.Panel, pubsub.pubsub): def __init__( self, parent, @@ -98,20 +98,23 @@ class number_window(wx.Panel, pubsub.pubsub, common.prop_setter): avg_alpha_key, peak_hold, msg_key, + sample_rate_key, ): pubsub.pubsub.__init__(self) - wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER) + wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) #setup self.peak_val_real = NEG_INF self.peak_val_imag = NEG_INF - self.ext_controller = controller self.real = real self.units = units self.minval = minval self.maxval = maxval self.decimal_places = decimal_places - self.average_key = average_key - self.avg_alpha_key = avg_alpha_key + #proxy the keys + self.proxy(MSG_KEY, controller, msg_key) + self.proxy(AVERAGE_KEY, controller, average_key) + self.proxy(AVG_ALPHA_KEY, controller, avg_alpha_key) + self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) #setup the box with display and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) @@ -128,13 +131,13 @@ class number_window(wx.Panel, pubsub.pubsub, common.prop_setter): sizer.Add(self.gauge_real, 1, wx.EXPAND) sizer.Add(self.gauge_imag, 1, wx.EXPAND) self.SetSizerAndFit(main_box) - #initial setup - self.ext_controller[self.average_key] = self.ext_controller[self.average_key] - self.ext_controller[self.avg_alpha_key] = self.ext_controller[self.avg_alpha_key] - self._register_set_prop(self, PEAK_HOLD_KEY, peak_hold) - self._register_set_prop(self, RUNNING_KEY, True) + #initialize values + self[PEAK_HOLD_KEY] = peak_hold + self[RUNNING_KEY] = True + self[AVERAGE_KEY] = self[AVERAGE_KEY] + self[AVG_ALPHA_KEY] = self[AVG_ALPHA_KEY] #register events - self.ext_controller.subscribe(msg_key, self.handle_msg) + self.subscribe(MSG_KEY, self.handle_msg) self.Bind(common.EVT_DATA, self.update) def show_gauges(self, show_gauge): diff --git a/gr-wxgui/src/python/numbersink2.py b/gr-wxgui/src/python/numbersink2.py index 9e52745d..5fa9e3ae 100644 --- a/gr-wxgui/src/python/numbersink2.py +++ b/gr-wxgui/src/python/numbersink2.py @@ -31,7 +31,7 @@ from constants import * ################################################## # Number sink block (wrapper for old wxgui) ################################################## -class _number_sink_base(gr.hier_block2, common.prop_setter): +class _number_sink_base(gr.hier_block2): """ An decimator block with a number window display """ @@ -74,29 +74,28 @@ class _number_sink_base(gr.hier_block2, common.prop_setter): if self._real: mult = gr.multiply_const_ff(factor) add = gr.add_const_ff(ref_level) - self._avg = gr.single_pole_iir_filter_ff(1.0) + avg = gr.single_pole_iir_filter_ff(1.0) else: mult = gr.multiply_const_cc(factor) add = gr.add_const_cc(ref_level) - self._avg = gr.single_pole_iir_filter_cc(1.0) + avg = gr.single_pole_iir_filter_cc(1.0) msgq = gr.msg_queue(2) sink = gr.message_sink(self._item_size, msgq, True) #connect - self.connect(self, sd, mult, add, self._avg, sink) - #setup averaging - self._avg_alpha = avg_alpha - self.set_average(average) - self.set_avg_alpha(avg_alpha) + self.connect(self, sd, mult, add, avg, sink) #controller self.controller = pubsub() self.controller.subscribe(SAMPLE_RATE_KEY, sd.set_sample_rate) - self.controller.subscribe(AVERAGE_KEY, self.set_average) - self.controller.publish(AVERAGE_KEY, self.get_average) - self.controller.subscribe(AVG_ALPHA_KEY, self.set_avg_alpha) - self.controller.publish(AVG_ALPHA_KEY, self.get_avg_alpha) + self.controller.publish(SAMPLE_RATE_KEY, sd.sample_rate) + def update_avg(*args): + if self.controller[AVERAGE_KEY]: avg.set_taps(self.controller[AVG_ALPHA_KEY]) + else: avg.set_taps(1.0) + self.controller.subscribe(AVERAGE_KEY, update_avg) + self.controller.subscribe(AVG_ALPHA_KEY, update_avg) + self.controller[AVERAGE_KEY] = average + self.controller[AVG_ALPHA_KEY] = avg_alpha #start input watcher - def set_msg(msg): self.controller[MSG_KEY] = msg - common.input_watcher(msgq, set_msg) + common.input_watcher(msgq, self.controller, MSG_KEY) #create window self.win = number_window.number_window( parent=parent, @@ -113,25 +112,12 @@ class _number_sink_base(gr.hier_block2, common.prop_setter): avg_alpha_key=AVG_ALPHA_KEY, peak_hold=peak_hold, msg_key=MSG_KEY, + sample_rate_key=SAMPLE_RATE_KEY, ) - #register callbacks from window for external use - for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): - setattr(self, attr, getattr(self.win, attr)) - self._register_set_prop(self.controller, SAMPLE_RATE_KEY) + common.register_access_methods(self, self.controller) #backwards compadibility self.set_show_gauge = self.win.show_gauges - def get_average(self): return self._average - def set_average(self, average): - self._average = average - if self.get_average(): self._avg.set_taps(self.get_avg_alpha()) - else: self._avg.set_taps(1.0) - - def get_avg_alpha(self): return self._avg_alpha - def set_avg_alpha(self, avg_alpha): - self._avg_alpha = avg_alpha - self.set_average(self.get_average()) - class number_sink_f(_number_sink_base): _item_size = gr.sizeof_float _real = True diff --git a/gr-wxgui/src/python/plotter/Makefile.am b/gr-wxgui/src/python/plotter/Makefile.am index ada50679..d00f0a42 100644 --- a/gr-wxgui/src/python/plotter/Makefile.am +++ b/gr-wxgui/src/python/plotter/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2004,2005,2008 Free Software Foundation, Inc. +# Copyright 2004,2005,2008,2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -30,8 +30,11 @@ ourlibdir = $(grpyexecdir)/wxgui/plotter ourpython_PYTHON = \ __init__.py \ + bar_plotter.py \ channel_plotter.py \ + common.py \ gltext.py \ + grid_plotter_base.py \ plotter_base.py \ waterfall_plotter.py diff --git a/gr-wxgui/src/python/plotter/__init__.py b/gr-wxgui/src/python/plotter/__init__.py index 12f8b345..616492a3 100644 --- a/gr-wxgui/src/python/plotter/__init__.py +++ b/gr-wxgui/src/python/plotter/__init__.py @@ -21,3 +21,4 @@ from channel_plotter import channel_plotter from waterfall_plotter import waterfall_plotter +from bar_plotter import bar_plotter diff --git a/gr-wxgui/src/python/plotter/bar_plotter.py b/gr-wxgui/src/python/plotter/bar_plotter.py new file mode 100644 index 00000000..3f9259e9 --- /dev/null +++ b/gr-wxgui/src/python/plotter/bar_plotter.py @@ -0,0 +1,144 @@ +# +# Copyright 2009 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import wx +from grid_plotter_base import grid_plotter_base +from OpenGL import GL +import common +import numpy + +LEGEND_TEXT_FONT_SIZE = 8 +LEGEND_BOX_PADDING = 3 +MIN_PADDING = 0, 0, 0, 70 #top, right, bottom, left +#constants for the waveform storage +SAMPLES_KEY = 'samples' +COLOR_SPEC_KEY = 'color_spec' +MARKERY_KEY = 'marker' +TRIG_OFF_KEY = 'trig_off' + +################################################## +# Bar Plotter for histogram waveforms +################################################## +class bar_plotter(grid_plotter_base): + + def __init__(self, parent): + """ + Create a new bar plotter. + """ + #init + grid_plotter_base.__init__(self, parent, MIN_PADDING) + self._bars = list() + self._bar_width = .5 + self._color_spec = (0, 0, 0) + #setup bar cache + self._bar_cache = self.new_gl_cache(self._draw_bars) + #setup bar plotter + self.register_init(self._init_bar_plotter) + + def _init_bar_plotter(self): + """ + Run gl initialization tasks. + """ + GL.glEnableClientState(GL.GL_VERTEX_ARRAY) + + def _draw_bars(self): + """ + Draw the vertical bars. + """ + bars = self._bars + num_bars = len(bars) + if num_bars == 0: return + #use scissor to prevent drawing outside grid + GL.glEnable(GL.GL_SCISSOR_TEST) + GL.glScissor( + self.padding_left, + self.padding_bottom+1, + self.width-self.padding_left-self.padding_right-1, + self.height-self.padding_top-self.padding_bottom-1, + ) + #load the points + points = list() + width = self._bar_width/2 + for i, bar in enumerate(bars): + points.extend([ + (i-width, 0), + (i+width, 0), + (i+width, bar), + (i-width, bar), + ] + ) + GL.glColor3f(*self._color_spec) + #matrix transforms + GL.glPushMatrix() + GL.glTranslatef(self.padding_left, self.padding_top, 0) + GL.glScalef( + (self.width-self.padding_left-self.padding_right), + (self.height-self.padding_top-self.padding_bottom), + 1, + ) + GL.glTranslatef(0, 1, 0) + GL.glScalef(1.0/(num_bars-1), -1.0/(self.y_max-self.y_min), 1) + GL.glTranslatef(0, -self.y_min, 0) + #draw the bars + GL.glVertexPointerf(points) + GL.glDrawArrays(GL.GL_QUADS, 0, len(points)) + GL.glPopMatrix() + GL.glDisable(GL.GL_SCISSOR_TEST) + + def _populate_point_label(self, x_val, y_val): + """ + Get the text the will populate the point label. + Give X and Y values for the current point. + Give values for the channel at the X coordinate. + @param x_val the current x value + @param y_val the current y value + @return a string with newlines + """ + if len(self._bars) == 0: return '' + scalar = float(len(self._bars)-1)/(self.x_max - self.x_min) + #convert x val to bar # + bar_index = scalar*(x_val - self.x_min) + #if abs(bar_index - round(bar_index)) > self._bar_width/2: return '' + bar_index = int(round(bar_index)) + bar_start = (bar_index - self._bar_width/2)/scalar + self.x_min + bar_end = (bar_index + self._bar_width/2)/scalar + self.x_min + bar_value = self._bars[bar_index] + return '%s to %s\n%s: %s'%( + common.eng_format(bar_start, self.x_units), + common.eng_format(bar_end, self.x_units), + self.y_label, common.eng_format(bar_value, self.y_units), + ) + + def set_bars(self, bars, bar_width, color_spec): + """ + Set the bars. + @param bars a list of bars + @param bar_width the fractional width of the bar, between 0 and 1 + @param color_spec the color tuple + """ + self.lock() + self._bars = bars + self._bar_width = float(bar_width) + self._color_spec = color_spec + self._bar_cache.changed(True) + self.unlock() + + diff --git a/gr-wxgui/src/python/plotter/channel_plotter.py b/gr-wxgui/src/python/plotter/channel_plotter.py index 8fa28410..ff0a3a16 100644 --- a/gr-wxgui/src/python/plotter/channel_plotter.py +++ b/gr-wxgui/src/python/plotter/channel_plotter.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008, 2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -20,20 +20,21 @@ # import wx -from plotter_base import grid_plotter_base -from OpenGL.GL import * -from gnuradio.wxgui import common +from grid_plotter_base import grid_plotter_base +from OpenGL import GL +import common import numpy import gltext import math LEGEND_TEXT_FONT_SIZE = 8 LEGEND_BOX_PADDING = 3 -PADDING = 35, 15, 40, 60 #top, right, bottom, left +MIN_PADDING = 35, 10, 0, 0 #top, right, bottom, left #constants for the waveform storage SAMPLES_KEY = 'samples' COLOR_SPEC_KEY = 'color_spec' MARKERY_KEY = 'marker' +TRIG_OFF_KEY = 'trig_off' ################################################## # Channel Plotter for X Y Waveforms @@ -45,16 +46,21 @@ class channel_plotter(grid_plotter_base): Create a new channel plotter. """ #init - grid_plotter_base.__init__(self, parent, PADDING) - self._channels = dict() + grid_plotter_base.__init__(self, parent, MIN_PADDING) + #setup legend cache + self._legend_cache = self.new_gl_cache(self._draw_legend, 50) self.enable_legend(False) + #setup waveform cache + self._waveform_cache = self.new_gl_cache(self._draw_waveforms, 50) + self._channels = dict() + #init channel plotter + self.register_init(self._init_channel_plotter) - def _gl_init(self): + def _init_channel_plotter(self): """ Run gl initialization tasks. """ - glEnableClientState(GL_VERTEX_ARRAY) - self._grid_compiled_list_id = glGenLists(1) + GL.glEnableClientState(GL.GL_VERTEX_ARRAY) def enable_legend(self, enable=None): """ @@ -65,73 +71,55 @@ class channel_plotter(grid_plotter_base): if enable is None: return self._enable_legend self.lock() self._enable_legend = enable - self.changed(True) + self._legend_cache.changed(True) self.unlock() - def draw(self): + def _draw_waveforms(self): """ - Draw the grid and waveforms. + Draw the waveforms for each channel. + Scale the waveform data to the grid using gl matrix operations. """ - self.lock() - self.clear() - #store the grid drawing operations - if self.changed(): - glNewList(self._grid_compiled_list_id, GL_COMPILE) - self._draw_grid() - self._draw_legend() - glEndList() - self.changed(False) - #draw the grid - glCallList(self._grid_compiled_list_id) #use scissor to prevent drawing outside grid - glEnable(GL_SCISSOR_TEST) - glScissor( + GL.glEnable(GL.GL_SCISSOR_TEST) + GL.glScissor( self.padding_left+1, self.padding_bottom+1, self.width-self.padding_left-self.padding_right-1, self.height-self.padding_top-self.padding_bottom-1, ) - #draw the waveforms - self._draw_waveforms() - glDisable(GL_SCISSOR_TEST) - self._draw_point_label() - #swap buffer into display - self.SwapBuffers() - self.unlock() - - def _draw_waveforms(self): - """ - Draw the waveforms for each channel. - Scale the waveform data to the grid using gl matrix operations. - """ for channel in reversed(sorted(self._channels.keys())): samples = self._channels[channel][SAMPLES_KEY] num_samps = len(samples) if not num_samps: continue #use opengl to scale the waveform - glPushMatrix() - glTranslatef(self.padding_left, self.padding_top, 0) - glScalef( + GL.glPushMatrix() + GL.glTranslatef(self.padding_left, self.padding_top, 0) + GL.glScalef( (self.width-self.padding_left-self.padding_right), (self.height-self.padding_top-self.padding_bottom), 1, ) - glTranslatef(0, 1, 0) + GL.glTranslatef(0, 1, 0) if isinstance(samples, tuple): x_scale, x_trans = 1.0/(self.x_max-self.x_min), -self.x_min points = zip(*samples) else: - x_scale, x_trans = 1.0/(num_samps-1), 0 + x_scale, x_trans = 1.0/(num_samps-1), -self._channels[channel][TRIG_OFF_KEY] points = zip(numpy.arange(0, num_samps), samples) - glScalef(x_scale, -1.0/(self.y_max-self.y_min), 1) - glTranslatef(x_trans, -self.y_min, 0) + GL.glScalef(x_scale, -1.0/(self.y_max-self.y_min), 1) + GL.glTranslatef(x_trans, -self.y_min, 0) #draw the points/lines - glColor3f(*self._channels[channel][COLOR_SPEC_KEY]) + GL.glColor3f(*self._channels[channel][COLOR_SPEC_KEY]) marker = self._channels[channel][MARKERY_KEY] - if marker: glPointSize(marker) - glVertexPointerf(points) - glDrawArrays(marker is None and GL_LINE_STRIP or GL_POINTS, 0, len(points)) - glPopMatrix() + if marker is None: + GL.glVertexPointerf(points) + GL.glDrawArrays(GL.GL_LINE_STRIP, 0, len(points)) + elif isinstance(marker, (int, float)) and marker > 0: + GL.glPointSize(marker) + GL.glVertexPointerf(points) + GL.glDrawArrays(GL.GL_POINTS, 0, len(points)) + GL.glPopMatrix() + GL.glDisable(GL.GL_SCISSOR_TEST) def _populate_point_label(self, x_val, y_val): """ @@ -143,12 +131,9 @@ class channel_plotter(grid_plotter_base): @return a string with newlines """ #create text - label_str = '%s: %s %s\n%s: %s %s'%( - self.x_label, - common.label_format(x_val), - self.x_units, self.y_label, - common.label_format(y_val), - self.y_units, + label_str = '%s: %s\n%s: %s'%( + self.x_label, common.eng_format(x_val, self.x_units), + self.y_label, common.eng_format(y_val, self.y_units), ) for channel in sorted(self._channels.keys()): samples = self._channels[channel][SAMPLES_KEY] @@ -156,11 +141,12 @@ class channel_plotter(grid_plotter_base): if not num_samps: continue if isinstance(samples, tuple): continue #linear interpolation - x_index = (num_samps-1)*(x_val/self.x_scalar-self.x_min)/(self.x_max-self.x_min) + x_index = (num_samps-1)*(x_val-self.x_min)/(self.x_max-self.x_min) x_index_low = int(math.floor(x_index)) x_index_high = int(math.ceil(x_index)) - y_value = (samples[x_index_high] - samples[x_index_low])*(x_index - x_index_low) + samples[x_index_low] - label_str += '\n%s: %s %s'%(channel, common.label_format(y_value), self.y_units) + scale = x_index - x_index_low + self._channels[channel][TRIG_OFF_KEY] + y_value = (samples[x_index_high] - samples[x_index_low])*scale + samples[x_index_low] + label_str += '\n%s: %s'%(channel, common.eng_format(y_value, self.y_units)) return label_str def _draw_legend(self): @@ -178,7 +164,7 @@ class channel_plotter(grid_plotter_base): txt = gltext.Text(channel, font_size=LEGEND_TEXT_FONT_SIZE) w, h = txt.get_size() #draw rect + text - glColor3f(*color_spec) + GL.glColor3f(*color_spec) self._draw_rect( x_off - w - LEGEND_BOX_PADDING, self.padding_top/2 - h/2 - LEGEND_BOX_PADDING, @@ -188,21 +174,36 @@ class channel_plotter(grid_plotter_base): txt.draw_text(wx.Point(x_off - w, self.padding_top/2 - h/2)) x_off -= w + 4*LEGEND_BOX_PADDING - def set_waveform(self, channel, samples, color_spec, marker=None): + def clear_waveform(self, channel): + """ + Remove a waveform from the list of waveforms. + @param channel the channel key + """ + self.lock() + if channel in self._channels.keys(): + self._channels.pop(channel) + self._legend_cache.changed(True) + self._waveform_cache.changed(True) + self.unlock() + + def set_waveform(self, channel, samples=[], color_spec=(0, 0, 0), marker=None, trig_off=0): """ Set the waveform for a given channel. @param channel the channel key @param samples the waveform samples @param color_spec the 3-tuple for line color @param marker None for line + @param trig_off fraction of sample for trigger offset """ self.lock() - if channel not in self._channels.keys(): self.changed(True) + if channel not in self._channels.keys(): self._legend_cache.changed(True) self._channels[channel] = { SAMPLES_KEY: samples, COLOR_SPEC_KEY: color_spec, MARKERY_KEY: marker, + TRIG_OFF_KEY: trig_off, } + self._waveform_cache.changed(True) self.unlock() if __name__ == '__main__': diff --git a/gr-wxgui/src/python/plotter/common.py b/gr-wxgui/src/python/plotter/common.py new file mode 100644 index 00000000..7699986a --- /dev/null +++ b/gr-wxgui/src/python/plotter/common.py @@ -0,0 +1,131 @@ +# +# Copyright 2009 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import threading +import time +import math +import wx + +################################################## +# Number formatting +################################################## +def get_exp(num): + """ + Get the exponent of the number in base 10. + @param num the floating point number + @return the exponent as an integer + """ + if num == 0: return 0 + return int(math.floor(math.log10(abs(num)))) + +def get_si_components(num): + """ + Get the SI units for the number. + Extract the coeff and exponent of the number. + The exponent will be a multiple of 3. + @param num the floating point number + @return the tuple coeff, exp, prefix + """ + num = float(num) + exp = get_exp(num) + exp -= exp%3 + exp = min(max(exp, -24), 24) #bounds on SI table below + prefix = { + 24: 'Y', 21: 'Z', + 18: 'E', 15: 'P', + 12: 'T', 9: 'G', + 6: 'M', 3: 'k', + 0: '', + -3: 'm', -6: 'u', + -9: 'n', -12: 'p', + -15: 'f', -18: 'a', + -21: 'z', -24: 'y', + }[exp] + coeff = num/10**exp + return coeff, exp, prefix + +def sci_format(num): + """ + Format a floating point number into scientific notation. + @param num the number to format + @return a label string + """ + coeff, exp, prefix = get_si_components(num) + if -3 <= exp < 3: return '%g'%num + return '%.3ge%d'%(coeff, exp) + +def eng_format(num, units=''): + """ + Format a floating point number into engineering notation. + @param num the number to format + @param units the units to append + @return a label string + """ + coeff, exp, prefix = get_si_components(num) + if -3 <= exp < 3: return '%g'%num + return '%g%s%s%s'%(coeff, units and ' ' or '', prefix, units) + +################################################## +# Interface with thread safe lock/unlock +################################################## +class mutex(object): + _lock = threading.Lock() + def lock(self): self._lock.acquire() + def unlock(self): self._lock.release() + +################################################## +# Periodic update thread for point label +################################################## +class point_label_thread(threading.Thread, mutex): + + def __init__(self, plotter): + self._plotter = plotter + self._coor_queue = list() + #bind plotter mouse events + self._plotter.Bind(wx.EVT_MOTION, lambda evt: self.enqueue(evt.GetPosition())) + self._plotter.Bind(wx.EVT_LEAVE_WINDOW, lambda evt: self.enqueue(None)) + #start the thread + threading.Thread.__init__(self) + self.start() + + def enqueue(self, coor): + self.lock() + self._coor_queue.append(coor) + self.unlock() + + def run(self): + last_ts = time.time() + last_coor = coor = None + try: + while True: + time.sleep(1.0/30.0) + self.lock() + #get most recent coor change + if self._coor_queue: + coor = self._coor_queue[-1] + self._coor_queue = list() + self.unlock() + #update if coor change, or enough time expired + if last_coor != coor or (time.time() - last_ts) > (1.0/2.0): + self._plotter.set_point_label_coordinate(coor) + last_coor = coor + last_ts = time.time() + except wx.PyDeadObjectError: pass diff --git a/gr-wxgui/src/python/plotter/grid_plotter_base.py b/gr-wxgui/src/python/plotter/grid_plotter_base.py new file mode 100644 index 00000000..fd318ffa --- /dev/null +++ b/gr-wxgui/src/python/plotter/grid_plotter_base.py @@ -0,0 +1,370 @@ +# +# Copyright 2009 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import wx +import wx.glcanvas +from OpenGL import GL +import common +from plotter_base import plotter_base +import gltext +import math + +GRID_LINE_COLOR_SPEC = (.7, .7, .7) #gray +GRID_BORDER_COLOR_SPEC = (0, 0, 0) #black +TICK_TEXT_FONT_SIZE = 9 +TITLE_TEXT_FONT_SIZE = 13 +UNITS_TEXT_FONT_SIZE = 9 +AXIS_LABEL_PADDING = 5 +TICK_LABEL_PADDING = 5 +TITLE_LABEL_PADDING = 7 +POINT_LABEL_FONT_SIZE = 8 +POINT_LABEL_COLOR_SPEC = (1, 1, .5) +POINT_LABEL_PADDING = 3 +GRID_LINE_DASH_LEN = 4 + +################################################## +# Grid Plotter Base Class +################################################## +class grid_plotter_base(plotter_base): + + def __init__(self, parent, min_padding=(0, 0, 0, 0)): + plotter_base.__init__(self, parent) + #setup grid cache + self._grid_cache = self.new_gl_cache(self._draw_grid, 25) + self.enable_grid_lines(True) + #setup padding + self.padding_top_min, self.padding_right_min, self.padding_bottom_min, self.padding_left_min = min_padding + #store title and unit strings + self.set_title('Title') + self.set_x_label('X Label') + self.set_y_label('Y Label') + #init the grid to some value + self.set_x_grid(-1, 1, 1) + self.set_y_grid(-1, 1, 1) + #setup point label cache + self._point_label_cache = self.new_gl_cache(self._draw_point_label, 75) + self.enable_point_label(False) + self.set_point_label_coordinate(None) + common.point_label_thread(self) + #init grid plotter + self.register_init(self._init_grid_plotter) + + def _init_grid_plotter(self): + """ + Run gl initialization tasks. + """ + GL.glEnableClientState(GL.GL_VERTEX_ARRAY) + + def set_point_label_coordinate(self, coor): + """ + Set the point label coordinate. + @param coor the coordinate x, y tuple or None + """ + self.lock() + self._point_label_coordinate = coor + self._point_label_cache.changed(True) + self.update() + self.unlock() + + def enable_point_label(self, enable=None): + """ + Enable/disable the point label. + @param enable true to enable + @return the enable state when None + """ + if enable is None: return self._enable_point_label + self.lock() + self._enable_point_label = enable + self._point_label_cache.changed(True) + self.unlock() + + def set_title(self, title): + """ + Set the title. + @param title the title string + """ + self.lock() + self.title = title + self._grid_cache.changed(True) + self.unlock() + + def set_x_label(self, x_label, x_units=''): + """ + Set the x label and units. + @param x_label the x label string + @param x_units the x units string + """ + self.lock() + self.x_label = x_label + self.x_units = x_units + self._grid_cache.changed(True) + self.unlock() + + def set_y_label(self, y_label, y_units=''): + """ + Set the y label and units. + @param y_label the y label string + @param y_units the y units string + """ + self.lock() + self.y_label = y_label + self.y_units = y_units + self._grid_cache.changed(True) + self.unlock() + + def set_x_grid(self, minimum, maximum, step, scale=False): + """ + Set the x grid parameters. + @param minimum the left-most value + @param maximum the right-most value + @param step the grid spacing + @param scale true to scale the x grid + """ + self.lock() + self.x_min = float(minimum) + self.x_max = float(maximum) + self.x_step = float(step) + if scale: + coeff, exp, prefix = common.get_si_components(max(abs(self.x_min), abs(self.x_max))) + self.x_scalar = 10**(-exp) + self.x_prefix = prefix + else: + self.x_scalar = 1.0 + self.x_prefix = '' + for cache in self._gl_caches: cache.changed(True) + self.unlock() + + def set_y_grid(self, minimum, maximum, step, scale=False): + """ + Set the y grid parameters. + @param minimum the bottom-most value + @param maximum the top-most value + @param step the grid spacing + @param scale true to scale the y grid + """ + self.lock() + self.y_min = float(minimum) + self.y_max = float(maximum) + self.y_step = float(step) + if scale: + coeff, exp, prefix = common.get_si_components(max(abs(self.y_min), abs(self.y_max))) + self.y_scalar = 10**(-exp) + self.y_prefix = prefix + else: + self.y_scalar = 1.0 + self.y_prefix = '' + for cache in self._gl_caches: cache.changed(True) + self.unlock() + + def _draw_grid(self): + """ + Create the x, y, tick, and title labels. + Resize the padding for the labels. + Draw the border, grid, title, and labels. + """ + ################################################## + # Create GL text labels + ################################################## + #create x tick labels + x_tick_labels = [(tick, self._get_tick_label(tick, self.x_units)) + for tick in self._get_ticks(self.x_min, self.x_max, self.x_step, self.x_scalar)] + #create x tick labels + y_tick_labels = [(tick, self._get_tick_label(tick, self.y_units)) + for tick in self._get_ticks(self.y_min, self.y_max, self.y_step, self.y_scalar)] + #create x label + x_label_str = self.x_units and "%s (%s%s)"%(self.x_label, self.x_prefix, self.x_units) or self.x_label + x_label = gltext.Text(x_label_str, bold=True, font_size=UNITS_TEXT_FONT_SIZE, centered=True) + #create y label + y_label_str = self.y_units and "%s (%s%s)"%(self.y_label, self.y_prefix, self.y_units) or self.y_label + y_label = gltext.Text(y_label_str, bold=True, font_size=UNITS_TEXT_FONT_SIZE, centered=True) + #create title + title_label = gltext.Text(self.title, bold=True, font_size=TITLE_TEXT_FONT_SIZE, centered=True) + ################################################## + # Resize the padding + ################################################## + self.padding_top = max(2*TITLE_LABEL_PADDING + title_label.get_size()[1], self.padding_top_min) + self.padding_right = max(2*TICK_LABEL_PADDING, self.padding_right_min) + self.padding_bottom = max(2*AXIS_LABEL_PADDING + TICK_LABEL_PADDING + x_label.get_size()[1] + max([label.get_size()[1] for tick, label in x_tick_labels]), self.padding_bottom_min) + self.padding_left = max(2*AXIS_LABEL_PADDING + TICK_LABEL_PADDING + y_label.get_size()[1] + max([label.get_size()[0] for tick, label in y_tick_labels]), self.padding_left_min) + ################################################## + # Draw Grid X + ################################################## + for tick, label in x_tick_labels: + scaled_tick = (self.width-self.padding_left-self.padding_right)*\ + (tick/self.x_scalar-self.x_min)/(self.x_max-self.x_min) + self.padding_left + self._draw_grid_line( + (scaled_tick, self.padding_top), + (scaled_tick, self.height-self.padding_bottom), + ) + w, h = label.get_size() + label.draw_text(wx.Point(scaled_tick-w/2, self.height-self.padding_bottom+TICK_LABEL_PADDING)) + ################################################## + # Draw Grid Y + ################################################## + for tick, label in y_tick_labels: + scaled_tick = (self.height-self.padding_top-self.padding_bottom)*\ + (1 - (tick/self.y_scalar-self.y_min)/(self.y_max-self.y_min)) + self.padding_top + self._draw_grid_line( + (self.padding_left, scaled_tick), + (self.width-self.padding_right, scaled_tick), + ) + w, h = label.get_size() + label.draw_text(wx.Point(self.padding_left-w-TICK_LABEL_PADDING, scaled_tick-h/2)) + ################################################## + # Draw Border + ################################################## + GL.glColor3f(*GRID_BORDER_COLOR_SPEC) + self._draw_rect( + self.padding_left, + self.padding_top, + self.width - self.padding_right - self.padding_left, + self.height - self.padding_top - self.padding_bottom, + fill=False, + ) + ################################################## + # Draw Labels + ################################################## + #draw title label + title_label.draw_text(wx.Point(self.width/2.0, TITLE_LABEL_PADDING + title_label.get_size()[1]/2)) + #draw x labels + x_label.draw_text(wx.Point( + (self.width-self.padding_left-self.padding_right)/2.0 + self.padding_left, + self.height-(AXIS_LABEL_PADDING + x_label.get_size()[1]/2), + ) + ) + #draw y labels + y_label.draw_text(wx.Point( + AXIS_LABEL_PADDING + y_label.get_size()[1]/2, + (self.height-self.padding_top-self.padding_bottom)/2.0 + self.padding_top, + ), rotation=90, + ) + + def _get_tick_label(self, tick, unit): + """ + Format the tick value and create a gl text. + @param tick the floating point tick value + @param unit the axis unit + @return the tick label text + """ + if unit: tick_str = common.sci_format(tick) + else: tick_str = common.eng_format(tick) + return gltext.Text(tick_str, font_size=TICK_TEXT_FONT_SIZE) + + def _get_ticks(self, min, max, step, scalar): + """ + Determine the positions for the ticks. + @param min the lower bound + @param max the upper bound + @param step the grid spacing + @param scalar the grid scaling + @return a list of tick positions between min and max + """ + #cast to float + min = float(min) + max = float(max) + step = float(step) + #check for valid numbers + try: + assert step > 0 + assert max > min + assert max - min > step + except AssertionError: return [-1, 1] + #determine the start and stop value + start = int(math.ceil(min/step)) + stop = int(math.floor(max/step)) + return [i*step*scalar for i in range(start, stop+1)] + + def enable_grid_lines(self, enable=None): + """ + Enable/disable the grid lines. + @param enable true to enable + @return the enable state when None + """ + if enable is None: return self._enable_grid_lines + self.lock() + self._enable_grid_lines = enable + self._grid_cache.changed(True) + self.unlock() + + def _draw_grid_line(self, coor1, coor2): + """ + Draw a dashed line from coor1 to coor2. + @param corr1 a tuple of x, y + @param corr2 a tuple of x, y + """ + if not self.enable_grid_lines(): return + length = math.sqrt((coor1[0] - coor2[0])**2 + (coor1[1] - coor2[1])**2) + num_points = int(length/GRID_LINE_DASH_LEN) + #calculate points array + points = [( + coor1[0] + i*(coor2[0]-coor1[0])/(num_points - 1), + coor1[1] + i*(coor2[1]-coor1[1])/(num_points - 1) + ) for i in range(num_points)] + #set color and draw + GL.glColor3f(*GRID_LINE_COLOR_SPEC) + GL.glVertexPointerf(points) + GL.glDrawArrays(GL.GL_LINES, 0, len(points)) + + def _draw_rect(self, x, y, width, height, fill=True): + """ + Draw a rectangle on the x, y plane. + X and Y are the top-left corner. + @param x the left position of the rectangle + @param y the top position of the rectangle + @param width the width of the rectangle + @param height the height of the rectangle + @param fill true to color inside of rectangle + """ + GL.glBegin(fill and GL.GL_QUADS or GL.GL_LINE_LOOP) + GL.glVertex2f(x, y) + GL.glVertex2f(x+width, y) + GL.glVertex2f(x+width, y+height) + GL.glVertex2f(x, y+height) + GL.glEnd() + + def _draw_point_label(self): + """ + Draw the point label for the last mouse motion coordinate. + The mouse coordinate must be an X, Y tuple. + The label will be drawn at the X, Y coordinate. + The values of the X, Y coordinate will be scaled to the current X, Y bounds. + """ + if not self.enable_point_label(): return + if not self._point_label_coordinate: return + x, y = self._point_label_coordinate + if x < self.padding_left or x > self.width-self.padding_right: return + if y < self.padding_top or y > self.height-self.padding_bottom: return + #scale to window bounds + x_win_scalar = float(x - self.padding_left)/(self.width-self.padding_left-self.padding_right) + y_win_scalar = float((self.height - y) - self.padding_bottom)/(self.height-self.padding_top-self.padding_bottom) + #scale to grid bounds + x_val = x_win_scalar*(self.x_max-self.x_min) + self.x_min + y_val = y_win_scalar*(self.y_max-self.y_min) + self.y_min + #create text + label_str = self._populate_point_label(x_val, y_val) + if not label_str: return + txt = gltext.Text(label_str, font_size=POINT_LABEL_FONT_SIZE) + w, h = txt.get_size() + #draw rect + text + GL.glColor3f(*POINT_LABEL_COLOR_SPEC) + if x > self.width/2: x -= w+2*POINT_LABEL_PADDING + self._draw_rect(x, y-h-2*POINT_LABEL_PADDING, w+2*POINT_LABEL_PADDING, h+2*POINT_LABEL_PADDING) + txt.draw_text(wx.Point(x+POINT_LABEL_PADDING, y-h-POINT_LABEL_PADDING)) diff --git a/gr-wxgui/src/python/plotter/plotter_base.py b/gr-wxgui/src/python/plotter/plotter_base.py index 6d5349a5..662365a3 100644 --- a/gr-wxgui/src/python/plotter/plotter_base.py +++ b/gr-wxgui/src/python/plotter/plotter_base.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008, 2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -21,27 +21,59 @@ import wx import wx.glcanvas -from OpenGL.GL import * -from gnuradio.wxgui import common -import threading -import gltext -import math -import time +from OpenGL import GL +import common BACKGROUND_COLOR_SPEC = (1, 0.976, 1, 1) #creamy white -GRID_LINE_COLOR_SPEC = (0, 0, 0) #black -TICK_TEXT_FONT_SIZE = 9 -TITLE_TEXT_FONT_SIZE = 13 -UNITS_TEXT_FONT_SIZE = 9 -TICK_LABEL_PADDING = 5 -POINT_LABEL_FONT_SIZE = 8 -POINT_LABEL_COLOR_SPEC = (1, 1, .5) -POINT_LABEL_PADDING = 3 + +################################################## +# GL caching interface +################################################## +class gl_cache(object): + """ + Cache a set of gl drawing routines in a compiled list. + """ + + def __init__(self, draw): + """ + Create a new cache. + @param draw a function to draw gl stuff + """ + self.changed(True) + self._draw = draw + + def init(self): + """ + To be called when gl initializes. + Create a new compiled list. + """ + self._grid_compiled_list_id = GL.glGenLists(1) + + def draw(self): + """ + Draw the gl stuff using a compiled list. + If changed, reload the compiled list. + """ + if self.changed(): + GL.glNewList(self._grid_compiled_list_id, GL.GL_COMPILE) + self._draw() + GL.glEndList() + self.changed(False) + #draw the grid + GL.glCallList(self._grid_compiled_list_id) + + def changed(self, state=None): + """ + Set the changed flag if state is not None. + Otherwise return the changed flag. + """ + if state is None: return self._changed + self._changed = state ################################################## # OpenGL WX Plotter Canvas ################################################## -class _plotter_base(wx.glcanvas.GLCanvas): +class plotter_base(wx.glcanvas.GLCanvas, common.mutex): """ Plotter base class for all plot types. """ @@ -54,340 +86,86 @@ class _plotter_base(wx.glcanvas.GLCanvas): Bind the paint and size events. @param parent the parent widgit """ - self._global_lock = threading.Lock() attribList = (wx.glcanvas.WX_GL_DOUBLEBUFFER, wx.glcanvas.WX_GL_RGBA) wx.glcanvas.GLCanvas.__init__(self, parent, attribList=attribList) - self.changed(False) self._gl_init_flag = False self._resized_flag = True - self._update_ts = 0 + self._init_fcns = list() + self._draw_fcns = list() + self._gl_caches = list() self.Bind(wx.EVT_PAINT, self._on_paint) self.Bind(wx.EVT_SIZE, self._on_size) + self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None) - def lock(self): self._global_lock.acquire() - def unlock(self): self._global_lock.release() + def new_gl_cache(self, draw_fcn, draw_pri=50): + """ + Create a new gl cache. + Register its draw and init function. + @return the new cache object + """ + cache = gl_cache(draw_fcn) + self.register_init(cache.init) + self.register_draw(cache.draw, draw_pri) + self._gl_caches.append(cache) + return cache + + def register_init(self, init_fcn): + self._init_fcns.append(init_fcn) + + def register_draw(self, draw_fcn, draw_pri=50): + """ + Register a draw function with a layer priority. + Large pri values are drawn last. + Small pri values are drawn first. + """ + for i in range(len(self._draw_fcns)): + if draw_pri < self._draw_fcns[i][0]: + self._draw_fcns.insert(i, (draw_pri, draw_fcn)) + return + self._draw_fcns.append((draw_pri, draw_fcn)) def _on_size(self, event): """ Flag the resize event. The paint event will handle the actual resizing. """ + self.lock() self._resized_flag = True + self.unlock() def _on_paint(self, event): """ - Respond to paint events, call update. + Respond to paint events. Initialize GL if this is the first paint event. + Resize the view port if the width or height changed. + Redraw the screen, calling the draw functions. """ + self.lock() self.SetCurrent() #check if gl was initialized if not self._gl_init_flag: - glClearColor(*BACKGROUND_COLOR_SPEC) - self._gl_init() + GL.glClearColor(*BACKGROUND_COLOR_SPEC) + for fcn in self._init_fcns: fcn() self._gl_init_flag = True #check for a change in window size if self._resized_flag: - self.lock() self.width, self.height = self.GetSize() - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - glOrtho(0, self.width, self.height, 0, 1, 0) - glMatrixMode(GL_MODELVIEW) - glLoadIdentity() - glViewport(0, 0, self.width, self.height) + GL.glMatrixMode(GL.GL_PROJECTION) + GL.glLoadIdentity() + GL.glOrtho(0, self.width, self.height, 0, 1, 0) + GL.glMatrixMode(GL.GL_MODELVIEW) + GL.glLoadIdentity() + GL.glViewport(0, 0, self.width, self.height) + for cache in self._gl_caches: cache.changed(True) self._resized_flag = False - self.changed(True) - self.unlock() - self.draw() + #clear, draw functions, swap + GL.glClear(GL.GL_COLOR_BUFFER_BIT) + for fcn in self._draw_fcns: fcn[1]() + self.SwapBuffers() + self.unlock() def update(self): """ Force a paint event. - Record the timestamp. """ wx.PostEvent(self, wx.PaintEvent()) - self._update_ts = time.time() - - def clear(self): glClear(GL_COLOR_BUFFER_BIT) - - def changed(self, state=None): - """ - Set the changed flag if state is not None. - Otherwise return the changed flag. - """ - if state is not None: self._changed = state - else: return self._changed - -################################################## -# Grid Plotter Base Class -################################################## -class grid_plotter_base(_plotter_base): - - def __init__(self, parent, padding): - _plotter_base.__init__(self, parent) - self.padding_top, self.padding_right, self.padding_bottom, self.padding_left = padding - #store title and unit strings - self.set_title('Title') - self.set_x_label('X Label') - self.set_y_label('Y Label') - #init the grid to some value - self.set_x_grid(-1, 1, 1) - self.set_y_grid(-1, 1, 1) - #setup point label - self.enable_point_label(False) - self._mouse_coordinate = None - self.Bind(wx.EVT_MOTION, self._on_motion) - self.Bind(wx.EVT_LEAVE_WINDOW, self._on_leave_window) - - def _on_motion(self, event): - """ - Mouse motion, record the position X, Y. - """ - self.lock() - self._mouse_coordinate = event.GetPosition() - #update based on last known update time - if time.time() - self._update_ts > 0.03: self.update() - self.unlock() - - def _on_leave_window(self, event): - """ - Mouse leave window, set the position to None. - """ - self.lock() - self._mouse_coordinate = None - self.update() - self.unlock() - - def enable_point_label(self, enable=None): - """ - Enable/disable the point label. - @param enable true to enable - @return the enable state when None - """ - if enable is None: return self._enable_point_label - self.lock() - self._enable_point_label = enable - self.changed(True) - self.unlock() - - def set_title(self, title): - """ - Set the title. - @param title the title string - """ - self.lock() - self.title = title - self.changed(True) - self.unlock() - - def set_x_label(self, x_label, x_units=''): - """ - Set the x label and units. - @param x_label the x label string - @param x_units the x units string - """ - self.lock() - self.x_label = x_label - self.x_units = x_units - self.changed(True) - self.unlock() - - def set_y_label(self, y_label, y_units=''): - """ - Set the y label and units. - @param y_label the y label string - @param y_units the y units string - """ - self.lock() - self.y_label = y_label - self.y_units = y_units - self.changed(True) - self.unlock() - - def set_x_grid(self, x_min, x_max, x_step, x_scalar=1.0): - """ - Set the x grid parameters. - @param x_min the left-most value - @param x_max the right-most value - @param x_step the grid spacing - @param x_scalar the scalar factor - """ - self.lock() - self.x_min = float(x_min) - self.x_max = float(x_max) - self.x_step = float(x_step) - self.x_scalar = float(x_scalar) - self.changed(True) - self.unlock() - - def set_y_grid(self, y_min, y_max, y_step, y_scalar=1.0): - """ - Set the y grid parameters. - @param y_min the bottom-most value - @param y_max the top-most value - @param y_step the grid spacing - @param y_scalar the scalar factor - """ - self.lock() - self.y_min = float(y_min) - self.y_max = float(y_max) - self.y_step = float(y_step) - self.y_scalar = float(y_scalar) - self.changed(True) - self.unlock() - - def _draw_grid(self): - """ - Draw the border, grid, title, and units. - """ - ################################################## - # Draw Border - ################################################## - glColor3f(*GRID_LINE_COLOR_SPEC) - self._draw_rect( - self.padding_left, - self.padding_top, - self.width - self.padding_right - self.padding_left, - self.height - self.padding_top - self.padding_bottom, - fill=False, - ) - ################################################## - # Draw Grid X - ################################################## - for tick in self._get_ticks(self.x_min, self.x_max, self.x_step, self.x_scalar): - scaled_tick = (self.width-self.padding_left-self.padding_right)*\ - (tick/self.x_scalar-self.x_min)/(self.x_max-self.x_min) + self.padding_left - glColor3f(*GRID_LINE_COLOR_SPEC) - self._draw_line( - (scaled_tick, self.padding_top, 0), - (scaled_tick, self.height-self.padding_bottom, 0), - ) - txt = self._get_tick_label(tick) - w, h = txt.get_size() - txt.draw_text(wx.Point(scaled_tick-w/2, self.height-self.padding_bottom+TICK_LABEL_PADDING)) - ################################################## - # Draw Grid Y - ################################################## - for tick in self._get_ticks(self.y_min, self.y_max, self.y_step, self.y_scalar): - scaled_tick = (self.height-self.padding_top-self.padding_bottom)*\ - (1 - (tick/self.y_scalar-self.y_min)/(self.y_max-self.y_min)) + self.padding_top - glColor3f(*GRID_LINE_COLOR_SPEC) - self._draw_line( - (self.padding_left, scaled_tick, 0), - (self.width-self.padding_right, scaled_tick, 0), - ) - txt = self._get_tick_label(tick) - w, h = txt.get_size() - txt.draw_text(wx.Point(self.padding_left-w-TICK_LABEL_PADDING, scaled_tick-h/2)) - ################################################## - # Draw Title - ################################################## - #draw x units - txt = gltext.Text(self.title, bold=True, font_size=TITLE_TEXT_FONT_SIZE, centered=True) - txt.draw_text(wx.Point(self.width/2.0, .5*self.padding_top)) - ################################################## - # Draw Labels - ################################################## - #draw x labels - x_label_str = self.x_units and "%s (%s)"%(self.x_label, self.x_units) or self.x_label - txt = gltext.Text(x_label_str, bold=True, font_size=UNITS_TEXT_FONT_SIZE, centered=True) - txt.draw_text(wx.Point( - (self.width-self.padding_left-self.padding_right)/2.0 + self.padding_left, - self.height-.25*self.padding_bottom, - ) - ) - #draw y labels - y_label_str = self.y_units and "%s (%s)"%(self.y_label, self.y_units) or self.y_label - txt = gltext.Text(y_label_str, bold=True, font_size=UNITS_TEXT_FONT_SIZE, centered=True) - txt.draw_text(wx.Point( - .25*self.padding_left, - (self.height-self.padding_top-self.padding_bottom)/2.0 + self.padding_top, - ), rotation=90, - ) - - def _get_tick_label(self, tick): - """ - Format the tick value and create a gl text. - @param tick the floating point tick value - @return the tick label text - """ - tick_str = common.label_format(tick) - return gltext.Text(tick_str, font_size=TICK_TEXT_FONT_SIZE) - - def _get_ticks(self, min, max, step, scalar): - """ - Determine the positions for the ticks. - @param min the lower bound - @param max the upper bound - @param step the grid spacing - @param scalar the grid scaling - @return a list of tick positions between min and max - """ - #cast to float - min = float(min) - max = float(max) - step = float(step) - #check for valid numbers - assert step > 0 - assert max > min - assert max - min > step - #determine the start and stop value - start = int(math.ceil(min/step)) - stop = int(math.floor(max/step)) - return [i*step*scalar for i in range(start, stop+1)] - - def _draw_line(self, coor1, coor2): - """ - Draw a line from coor1 to coor2. - @param corr1 a tuple of x, y, z - @param corr2 a tuple of x, y, z - """ - glBegin(GL_LINES) - glVertex3f(*coor1) - glVertex3f(*coor2) - glEnd() - - def _draw_rect(self, x, y, width, height, fill=True): - """ - Draw a rectangle on the x, y plane. - X and Y are the top-left corner. - @param x the left position of the rectangle - @param y the top position of the rectangle - @param width the width of the rectangle - @param height the height of the rectangle - @param fill true to color inside of rectangle - """ - glBegin(fill and GL_QUADS or GL_LINE_LOOP) - glVertex2f(x, y) - glVertex2f(x+width, y) - glVertex2f(x+width, y+height) - glVertex2f(x, y+height) - glEnd() - - def _draw_point_label(self): - """ - Draw the point label for the last mouse motion coordinate. - The mouse coordinate must be an X, Y tuple. - The label will be drawn at the X, Y coordinate. - The values of the X, Y coordinate will be scaled to the current X, Y bounds. - """ - if not self.enable_point_label(): return - if not self._mouse_coordinate: return - x, y = self._mouse_coordinate - if x < self.padding_left or x > self.width-self.padding_right: return - if y < self.padding_top or y > self.height-self.padding_bottom: return - #scale to window bounds - x_win_scalar = float(x - self.padding_left)/(self.width-self.padding_left-self.padding_right) - y_win_scalar = float((self.height - y) - self.padding_bottom)/(self.height-self.padding_top-self.padding_bottom) - #scale to grid bounds - x_val = self.x_scalar*(x_win_scalar*(self.x_max-self.x_min) + self.x_min) - y_val = self.y_scalar*(y_win_scalar*(self.y_max-self.y_min) + self.y_min) - #create text - label_str = self._populate_point_label(x_val, y_val) - txt = gltext.Text(label_str, font_size=POINT_LABEL_FONT_SIZE) - w, h = txt.get_size() - #draw rect + text - glColor3f(*POINT_LABEL_COLOR_SPEC) - if x > self.width/2: x -= w+2*POINT_LABEL_PADDING - self._draw_rect(x, y-h-2*POINT_LABEL_PADDING, w+2*POINT_LABEL_PADDING, h+2*POINT_LABEL_PADDING) - txt.draw_text(wx.Point(x+POINT_LABEL_PADDING, y-h-POINT_LABEL_PADDING)) diff --git a/gr-wxgui/src/python/plotter/waterfall_plotter.py b/gr-wxgui/src/python/plotter/waterfall_plotter.py index 4dc19f67..2e066996 100644 --- a/gr-wxgui/src/python/plotter/waterfall_plotter.py +++ b/gr-wxgui/src/python/plotter/waterfall_plotter.py @@ -1,5 +1,5 @@ # -# Copyright 2008 Free Software Foundation, Inc. +# Copyright 2008, 2009 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -20,9 +20,9 @@ # import wx -from plotter_base import grid_plotter_base -from OpenGL.GL import * -from gnuradio.wxgui import common +from grid_plotter_base import grid_plotter_base +from OpenGL import GL +import common import numpy import gltext import math @@ -33,7 +33,7 @@ LEGEND_NUM_LABELS = 9 LEGEND_WIDTH = 8 LEGEND_FONT_SIZE = 8 LEGEND_BORDER_COLOR_SPEC = (0, 0, 0) #black -PADDING = 35, 60, 40, 60 #top, right, bottom, left +MIN_PADDING = 0, 60, 0, 0 #top, right, bottom, left ceil_log2 = lambda x: 2**int(math.ceil(math.log(x)/math.log(2))) @@ -91,7 +91,13 @@ class waterfall_plotter(grid_plotter_base): Create a new channel plotter. """ #init - grid_plotter_base.__init__(self, parent, PADDING) + grid_plotter_base.__init__(self, parent, MIN_PADDING) + #setup legend cache + self._legend_cache = self.new_gl_cache(self._draw_legend) + #setup waterfall cache + self._waterfall_cache = self.new_gl_cache(self._draw_waterfall, 50) + #setup waterfall plotter + self.register_init(self._init_waterfall) self._resize_texture(False) self._minimum = 0 self._maximum = 0 @@ -102,35 +108,11 @@ class waterfall_plotter(grid_plotter_base): self.set_num_lines(0) self.set_color_mode(COLORS.keys()[0]) - def _gl_init(self): + def _init_waterfall(self): """ Run gl initialization tasks. """ - self._grid_compiled_list_id = glGenLists(1) - self._waterfall_texture = glGenTextures(1) - - def draw(self): - """ - Draw the grid and waveforms. - """ - self.lock() - #resize texture - self._resize_texture() - #store the grid drawing operations - if self.changed(): - glNewList(self._grid_compiled_list_id, GL_COMPILE) - self._draw_grid() - self._draw_legend() - glEndList() - self.changed(False) - self.clear() - #draw the grid - glCallList(self._grid_compiled_list_id) - self._draw_waterfall() - self._draw_point_label() - #swap buffer into display - self.SwapBuffers() - self.unlock() + self._waterfall_texture = GL.glGenTextures(1) def _draw_waterfall(self): """ @@ -138,42 +120,44 @@ class waterfall_plotter(grid_plotter_base): The texture is circularly filled and will wrap around. Use matrix modeling to shift and scale the texture onto the coordinate plane. """ + #resize texture + self._resize_texture() #setup texture - glBindTexture(GL_TEXTURE_2D, self._waterfall_texture) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT) - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) + GL.glBindTexture(GL.GL_TEXTURE_2D, self._waterfall_texture) + GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR) + GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR) + GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT) + GL.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE) #write the buffer to the texture while self._buffer: - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, self._pointer, self._fft_size, 1, GL_RGBA, GL_UNSIGNED_BYTE, self._buffer.pop(0)) + GL.glTexSubImage2D(GL.GL_TEXTURE_2D, 0, 0, self._pointer, self._fft_size, 1, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, self._buffer.pop(0)) self._pointer = (self._pointer + 1)%self._num_lines #begin drawing - glEnable(GL_TEXTURE_2D) - glPushMatrix() + GL.glEnable(GL.GL_TEXTURE_2D) + GL.glPushMatrix() #matrix scaling - glTranslatef(self.padding_left, self.padding_top, 0) - glScalef( + GL.glTranslatef(self.padding_left, self.padding_top, 0) + GL.glScalef( float(self.width-self.padding_left-self.padding_right), float(self.height-self.padding_top-self.padding_bottom), 1.0, ) #draw texture with wrapping - glBegin(GL_QUADS) + GL.glBegin(GL.GL_QUADS) prop_y = float(self._pointer)/(self._num_lines-1) prop_x = float(self._fft_size)/ceil_log2(self._fft_size) off = 1.0/(self._num_lines-1) - glTexCoord2f(0, prop_y+1-off) - glVertex2f(0, 1) - glTexCoord2f(prop_x, prop_y+1-off) - glVertex2f(1, 1) - glTexCoord2f(prop_x, prop_y) - glVertex2f(1, 0) - glTexCoord2f(0, prop_y) - glVertex2f(0, 0) - glEnd() - glPopMatrix() - glDisable(GL_TEXTURE_2D) + GL.glTexCoord2f(0, prop_y+1-off) + GL.glVertex2f(0, 1) + GL.glTexCoord2f(prop_x, prop_y+1-off) + GL.glVertex2f(1, 1) + GL.glTexCoord2f(prop_x, prop_y) + GL.glVertex2f(1, 0) + GL.glTexCoord2f(0, prop_y) + GL.glVertex2f(0, 0) + GL.glEnd() + GL.glPopMatrix() + GL.glDisable(GL.GL_TEXTURE_2D) def _populate_point_label(self, x_val, y_val): """ @@ -183,7 +167,7 @@ class waterfall_plotter(grid_plotter_base): @param y_val the current y value @return a value string with units """ - return '%s: %s %s'%(self.x_label, common.label_format(x_val), self.x_units) + return '%s: %s'%(self.x_label, common.eng_format(x_val, self.x_units)) def _draw_legend(self): """ @@ -196,11 +180,11 @@ class waterfall_plotter(grid_plotter_base): x = self.width - self.padding_right + LEGEND_LEFT_PAD for i in range(LEGEND_NUM_BLOCKS): color = COLORS[self._color_mode][int(255*i/float(LEGEND_NUM_BLOCKS-1))] - glColor4f(*map(lambda c: ord(c)/255.0, color)) + GL.glColor4f(*map(lambda c: ord(c)/255.0, color)) y = self.height - (i+1)*block_height - self.padding_bottom self._draw_rect(x, y, LEGEND_WIDTH, block_height) #draw rectangle around color scale border - glColor3f(*LEGEND_BORDER_COLOR_SPEC) + GL.glColor3f(*LEGEND_BORDER_COLOR_SPEC) self._draw_rect(x, self.padding_top, LEGEND_WIDTH, legend_height, fill=False) #draw each legend label label_spacing = float(legend_height)/(LEGEND_NUM_LABELS-1) @@ -224,9 +208,9 @@ class waterfall_plotter(grid_plotter_base): self._buffer = list() self._pointer = 0 if self._num_lines and self._fft_size: - glBindTexture(GL_TEXTURE_2D, self._waterfall_texture) + GL.glBindTexture(GL.GL_TEXTURE_2D, self._waterfall_texture) data = numpy.zeros(self._num_lines*self._fft_size*4, numpy.uint8).tostring() - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ceil_log2(self._fft_size), self._num_lines, 0, GL_RGBA, GL_UNSIGNED_BYTE, data) + GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, ceil_log2(self._fft_size), self._num_lines, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, data) self._resize_texture_flag = False def set_color_mode(self, color_mode): @@ -239,7 +223,7 @@ class waterfall_plotter(grid_plotter_base): self.lock() if color_mode in COLORS.keys(): self._color_mode = color_mode - self.changed(True) + self._legend_cache.changed(True) self.update() self.unlock() @@ -268,7 +252,7 @@ class waterfall_plotter(grid_plotter_base): if self._minimum != minimum or self._maximum != maximum: self._minimum = minimum self._maximum = maximum - self.changed(True) + self._legend_cache.changed(True) if self._fft_size != len(samples): self._fft_size = len(samples) self._resize_texture(True) @@ -279,4 +263,5 @@ class waterfall_plotter(grid_plotter_base): #convert the samples to RGBA data data = numpy.choose(samples, COLORS[self._color_mode]).tostring() self._buffer.append(data) + self._waterfall_cache.changed(True) self.unlock() diff --git a/gr-wxgui/src/python/pubsub.py b/gr-wxgui/src/python/pubsub.py index cc8ea5cc..e55d6919 100644 --- a/gr-wxgui/src/python/pubsub.py +++ b/gr-wxgui/src/python/pubsub.py @@ -28,73 +28,73 @@ This is a proof of concept implementation, will likely change significantly. class pubsub(dict): def __init__(self): - self._publishers = { } - self._subscribers = { } - self._proxies = { } - + self._publishers = { } + self._subscribers = { } + self._proxies = { } + def __missing__(self, key, value=None): - dict.__setitem__(self, key, value) - self._publishers[key] = None - self._subscribers[key] = [] - self._proxies[key] = None - + dict.__setitem__(self, key, value) + self._publishers[key] = None + self._subscribers[key] = [] + self._proxies[key] = None + def __setitem__(self, key, val): - if not self.has_key(key): - self.__missing__(key, val) - elif self._proxies[key] is not None: - (p, newkey) = self._proxies[key] - p[newkey] = val - else: - dict.__setitem__(self, key, val) - for sub in self._subscribers[key]: - # Note this means subscribers will get called in the thread - # context of the 'set' caller. - sub(val) + if not self.has_key(key): + self.__missing__(key, val) + elif self._proxies[key] is not None: + (p, pkey) = self._proxies[key] + p[pkey] = val + else: + dict.__setitem__(self, key, val) + for sub in self._subscribers[key]: + # Note this means subscribers will get called in the thread + # context of the 'set' caller. + sub(val) def __getitem__(self, key): - if not self.has_key(key): self.__missing__(key) - if self._proxies[key] is not None: - (p, newkey) = self._proxies[key] - return p[newkey] - elif self._publishers[key] is not None: - return self._publishers[key]() - else: - return dict.__getitem__(self, key) + if not self.has_key(key): self.__missing__(key) + if self._proxies[key] is not None: + (p, pkey) = self._proxies[key] + return p[pkey] + elif self._publishers[key] is not None: + return self._publishers[key]() + else: + return dict.__getitem__(self, key) def publish(self, key, publisher): - if not self.has_key(key): self.__missing__(key) + if not self.has_key(key): self.__missing__(key) if self._proxies[key] is not None: - (p, newkey) = self._proxies[key] - p.publish(newkey, publisher) + (p, pkey) = self._proxies[key] + p.publish(pkey, publisher) else: self._publishers[key] = publisher - + def subscribe(self, key, subscriber): - if not self.has_key(key): self.__missing__(key) + if not self.has_key(key): self.__missing__(key) if self._proxies[key] is not None: - (p, newkey) = self._proxies[key] - p.subscribe(newkey, subscriber) + (p, pkey) = self._proxies[key] + p.subscribe(pkey, subscriber) else: self._subscribers[key].append(subscriber) - + def unpublish(self, key): if self._proxies[key] is not None: - (p, newkey) = self._proxies[key] - p.unpublish(newkey) + (p, pkey) = self._proxies[key] + p.unpublish(pkey) else: self._publishers[key] = None - + def unsubscribe(self, key, subscriber): if self._proxies[key] is not None: - (p, newkey) = self._proxies[key] - p.unsubscribe(newkey, subscriber) + (p, pkey) = self._proxies[key] + p.unsubscribe(pkey, subscriber) else: self._subscribers[key].remove(subscriber) - def proxy(self, key, p, newkey=None): - if not self.has_key(key): self.__missing__(key) - if newkey is None: newkey = key - self._proxies[key] = (p, newkey) + def proxy(self, key, p, pkey=None): + if not self.has_key(key): self.__missing__(key) + if pkey is None: pkey = key + self._proxies[key] = (p, pkey) def unproxy(self, key): self._proxies[key] = None @@ -110,22 +110,22 @@ if __name__ == "__main__": # Add some subscribers # First is a bare function def print_len(x): - print "len=%i" % (len(x), ) + print "len=%i" % (len(x), ) o.subscribe('foo', print_len) # The second is a class member function class subber(object): - def __init__(self, param): - self._param = param - def printer(self, x): - print self._param, `x` + def __init__(self, param): + self._param = param + def printer(self, x): + print self._param, `x` s = subber('param') o.subscribe('foo', s.printer) # The third is a lambda function o.subscribe('foo', lambda x: sys.stdout.write('val='+`x`+'\n')) - # Update key 'foo', will notify subscribers + # Update key 'foo', will notify subscribers print "Updating 'foo' with three subscribers:" o['foo'] = 'bar'; @@ -135,7 +135,7 @@ if __name__ == "__main__": # Update now will only trigger second and third subscriber print "Updating 'foo' after removing a subscriber:" o['foo'] = 'bar2'; - + # Publish a key as a function, in this case, a lambda function o.publish('baz', lambda : 42) print "Published value of 'baz':", o['baz'] @@ -145,7 +145,7 @@ if __name__ == "__main__": # This will return None, as there is no publisher print "Value of 'baz' with no publisher:", o['baz'] - + # Set 'baz' key, it gets cached o['baz'] = 'bazzz' diff --git a/gr-wxgui/src/python/scope_window.py b/gr-wxgui/src/python/scope_window.py index 7d4f9711..bbc66426 100644 --- a/gr-wxgui/src/python/scope_window.py +++ b/gr-wxgui/src/python/scope_window.py @@ -29,41 +29,40 @@ import numpy import time import pubsub from constants import * -from gnuradio import gr #for gr.prefs +from gnuradio import gr #for gr.prefs, trigger modes ################################################## # Constants ################################################## DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'scope_rate', 30) DEFAULT_WIN_SIZE = (600, 300) -DEFAULT_V_SCALE = 1000 +COUPLING_MODES = ( + ('DC', False), + ('AC', True), +) TRIGGER_MODES = ( - ('Off', 0), - ('Neg', -1), - ('Pos', +1), + ('Freerun', gr.gr_TRIG_MODE_FREE), + ('Automatic', gr.gr_TRIG_MODE_AUTO), + ('Normal', gr.gr_TRIG_MODE_NORM), ) -TRIGGER_LEVELS = ( - ('Auto', None), - ('+High', 0.75), - ('+Med', 0.5), - ('+Low', 0.25), - ('Zero', 0.0), - ('-Low', -0.25), - ('-Med', -0.5), - ('-High', -0.75), +TRIGGER_SLOPES = ( + ('Positive +', gr.gr_TRIG_SLOPE_POS), + ('Negative -', gr.gr_TRIG_SLOPE_NEG), ) CHANNEL_COLOR_SPECS = ( - (0, 0, 1), - (0, 1, 0), - (1, 0, 0), - (1, 0, 1), + (0.3, 0.3, 1.0), + (0.0, 0.8, 0.0), + (1.0, 0.0, 0.0), + (0.8, 0.0, 0.8), ) +TRIGGER_COLOR_SPEC = (1.0, 0.4, 0.0) AUTORANGE_UPDATE_RATE = 0.5 #sec MARKER_TYPES = ( - ('Dot Small', 1.0), - ('Dot Medium', 2.0), - ('Dot Large', 3.0), ('Line Link', None), + ('Dot Large', 3.0), + ('Dot Med', 2.0), + ('Dot Small', 1.0), + ('None', 0.0), ) DEFAULT_MARKER_TYPE = None @@ -79,175 +78,213 @@ class control_panel(wx.Panel): Create a new control panel. @param parent the wx parent window """ + SIZE = (100, -1) self.parent = parent wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) - self.control_box = control_box = wx.BoxSizer(wx.VERTICAL) - #trigger options - control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Trigger Options'), 0, wx.ALIGN_CENTER) - control_box.AddSpacer(2) - #trigger mode - self.trigger_mode_chooser = common.DropDownController(self, 'Mode', TRIGGER_MODES, parent, TRIGGER_MODE_KEY) - control_box.Add(self.trigger_mode_chooser, 0, wx.EXPAND) - #trigger level - self.trigger_level_chooser = common.DropDownController(self, 'Level', TRIGGER_LEVELS, parent, TRIGGER_LEVEL_KEY) - parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_level_chooser.Disable(x==0)) - control_box.Add(self.trigger_level_chooser, 0, wx.EXPAND) - #trigger channel - choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)] - self.trigger_channel_chooser = common.DropDownController(self, 'Channel', choices, parent, TRIGGER_CHANNEL_KEY) - parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_channel_chooser.Disable(x==0)) - control_box.Add(self.trigger_channel_chooser, 0, wx.EXPAND) - #axes options - SPACING = 15 + control_box = wx.BoxSizer(wx.VERTICAL) + ################################################## + # Axes Options + ################################################## control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Axes Options'), 0, wx.ALIGN_CENTER) control_box.AddSpacer(2) ################################################## # Scope Mode Box ################################################## - self.scope_mode_box = wx.BoxSizer(wx.VERTICAL) - control_box.Add(self.scope_mode_box, 0, wx.EXPAND) + scope_mode_box = wx.BoxSizer(wx.VERTICAL) + control_box.Add(scope_mode_box, 0, wx.EXPAND) #x axis divs - hbox = wx.BoxSizer(wx.HORIZONTAL) - self.scope_mode_box.Add(hbox, 0, wx.EXPAND) - hbox.Add(wx.StaticText(self, -1, ' Secs/Div '), 1, wx.ALIGN_CENTER_VERTICAL) - x_buttons = common.IncrDecrButtons(self, self._on_incr_t_divs, self._on_decr_t_divs) - hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - hbox.AddSpacer(SPACING) + x_buttons_scope = common.IncrDecrButtons(self, self._on_incr_t_divs, self._on_decr_t_divs) + scope_mode_box.Add(common.LabelBox(self, 'Secs/Div', x_buttons_scope), 0, wx.EXPAND) #y axis divs - hbox = wx.BoxSizer(wx.HORIZONTAL) - self.scope_mode_box.Add(hbox, 0, wx.EXPAND) - hbox.Add(wx.StaticText(self, -1, ' Units/Div '), 1, wx.ALIGN_CENTER_VERTICAL) - y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs) - parent.subscribe(AUTORANGE_KEY, y_buttons.Disable) - hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - hbox.AddSpacer(SPACING) + y_buttons_scope = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs) + parent.subscribe(AUTORANGE_KEY, lambda x: y_buttons_scope.Enable(not x)) + scope_mode_box.Add(common.LabelBox(self, 'Counts/Div', y_buttons_scope), 0, wx.EXPAND) #y axis ref lvl - hbox = wx.BoxSizer(wx.HORIZONTAL) - self.scope_mode_box.Add(hbox, 0, wx.EXPAND) - hbox.Add(wx.StaticText(self, -1, ' Y Offset '), 1, wx.ALIGN_CENTER_VERTICAL) - y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off) - parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable) - hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - hbox.AddSpacer(SPACING) + y_off_buttons_scope = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off) + parent.subscribe(AUTORANGE_KEY, lambda x: y_off_buttons_scope.Enable(not x)) + scope_mode_box.Add(common.LabelBox(self, 'Y Offset', y_off_buttons_scope), 0, wx.EXPAND) + #t axis ref lvl + scope_mode_box.AddSpacer(5) + t_off_slider = wx.Slider(self, size=SIZE, style=wx.SL_HORIZONTAL) + t_off_slider.SetRange(0, 1000) + def t_off_slider_changed(evt): parent[T_FRAC_OFF_KEY] = float(t_off_slider.GetValue())/t_off_slider.GetMax() + t_off_slider.Bind(wx.EVT_SLIDER, t_off_slider_changed) + parent.subscribe(T_FRAC_OFF_KEY, lambda x: t_off_slider.SetValue(int(round(x*t_off_slider.GetMax())))) + scope_mode_box.Add(common.LabelBox(self, 'T Offset', t_off_slider), 0, wx.EXPAND) + scope_mode_box.AddSpacer(5) ################################################## # XY Mode Box ################################################## - self.xy_mode_box = wx.BoxSizer(wx.VERTICAL) - control_box.Add(self.xy_mode_box, 0, wx.EXPAND) - #x and y channel - CHOOSER_WIDTH = 60 - CENTER_SPACING = 10 - hbox = wx.BoxSizer(wx.HORIZONTAL) - self.xy_mode_box.Add(hbox, 0, wx.EXPAND) - choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)] - self.channel_x_chooser = common.DropDownController(self, 'X Ch', choices, parent, SCOPE_X_CHANNEL_KEY, (CHOOSER_WIDTH, -1)) - hbox.Add(self.channel_x_chooser, 0, wx.EXPAND) - hbox.AddSpacer(CENTER_SPACING) - self.channel_y_chooser = common.DropDownController(self, 'Y Ch', choices, parent, SCOPE_Y_CHANNEL_KEY, (CHOOSER_WIDTH, -1)) - hbox.Add(self.channel_y_chooser, 0, wx.EXPAND) - #div controls - hbox = wx.BoxSizer(wx.HORIZONTAL) - self.xy_mode_box.Add(hbox, 0, wx.EXPAND) - hbox.Add(wx.StaticText(self, -1, ' X/Div '), 1, wx.ALIGN_CENTER_VERTICAL) + xy_mode_box = wx.BoxSizer(wx.VERTICAL) + control_box.Add(xy_mode_box, 0, wx.EXPAND) + #x div controls x_buttons = common.IncrDecrButtons(self, self._on_incr_x_divs, self._on_decr_x_divs) - parent.subscribe(AUTORANGE_KEY, x_buttons.Disable) - hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - hbox.AddSpacer(CENTER_SPACING) - hbox.Add(wx.StaticText(self, -1, ' Y/Div '), 1, wx.ALIGN_CENTER_VERTICAL) + parent.subscribe(AUTORANGE_KEY, lambda x: x_buttons.Enable(not x)) + xy_mode_box.Add(common.LabelBox(self, 'X/Div', x_buttons), 0, wx.EXPAND) + #y div controls y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs) - parent.subscribe(AUTORANGE_KEY, y_buttons.Disable) - hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - #offset controls - hbox = wx.BoxSizer(wx.HORIZONTAL) - self.xy_mode_box.Add(hbox, 0, wx.EXPAND) - hbox.Add(wx.StaticText(self, -1, ' X Off '), 1, wx.ALIGN_CENTER_VERTICAL) + parent.subscribe(AUTORANGE_KEY, lambda x: y_buttons.Enable(not x)) + xy_mode_box.Add(common.LabelBox(self, 'Y/Div', y_buttons), 0, wx.EXPAND) + #x offset controls x_off_buttons = common.IncrDecrButtons(self, self._on_incr_x_off, self._on_decr_x_off) - parent.subscribe(AUTORANGE_KEY, x_off_buttons.Disable) - hbox.Add(x_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - hbox.AddSpacer(CENTER_SPACING) - hbox.Add(wx.StaticText(self, -1, ' Y Off '), 1, wx.ALIGN_CENTER_VERTICAL) + parent.subscribe(AUTORANGE_KEY, lambda x: x_off_buttons.Enable(not x)) + xy_mode_box.Add(common.LabelBox(self, 'X Off', x_off_buttons), 0, wx.EXPAND) + #y offset controls y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off) - parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable) - hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL) - ################################################## - # End Special Boxes - ################################################## - #misc options - control_box.AddStretchSpacer() - control_box.Add(common.LabelText(self, 'Range Options'), 0, wx.ALIGN_CENTER) - #ac couple check box - self.ac_couple_check_box = common.CheckBoxController(self, 'AC Couple', parent, AC_COUPLE_KEY) - control_box.Add(self.ac_couple_check_box, 0, wx.ALIGN_LEFT) + parent.subscribe(AUTORANGE_KEY, lambda x: y_off_buttons.Enable(not x)) + xy_mode_box.Add(common.LabelBox(self, 'Y Off', y_off_buttons), 0, wx.EXPAND) + xy_mode_box.ShowItems(False) #autorange check box self.autorange_check_box = common.CheckBoxController(self, 'Autorange', parent, AUTORANGE_KEY) control_box.Add(self.autorange_check_box, 0, wx.ALIGN_LEFT) - #marker control_box.AddStretchSpacer() - self.marker_chooser = common.DropDownController(self, 'Marker', MARKER_TYPES, parent, MARKER_KEY) - control_box.Add(self.marker_chooser, 0, wx.EXPAND) - #xy mode - control_box.AddStretchSpacer() - self.scope_xy_mode_button = common.ToggleButtonController(self, parent, SCOPE_XY_MODE_KEY, 'Scope Mode', 'X:Y Mode') - parent.subscribe(SCOPE_XY_MODE_KEY, self._on_scope_xy_mode) - control_box.Add(self.scope_xy_mode_button, 0, wx.EXPAND) + ################################################## + # Channel Options + ################################################## + TRIGGER_PAGE_INDEX = parent.num_inputs + XY_PAGE_INDEX = parent.num_inputs+1 + control_box.Add(common.LabelText(self, 'Channel Options'), 0, wx.ALIGN_CENTER) + control_box.AddSpacer(2) + options_notebook = wx.Notebook(self) + control_box.Add(options_notebook, 0, wx.EXPAND) + def options_notebook_changed(evt): + try: + parent[TRIGGER_SHOW_KEY] = options_notebook.GetSelection() == TRIGGER_PAGE_INDEX + parent[XY_MODE_KEY] = options_notebook.GetSelection() == XY_PAGE_INDEX + except wx.PyDeadObjectError: pass + options_notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, options_notebook_changed) + def xy_mode_changed(mode): + #ensure xy tab is selected + if mode and options_notebook.GetSelection() != XY_PAGE_INDEX: + options_notebook.SetSelection(XY_PAGE_INDEX) + #ensure xy tab is not selected + elif not mode and options_notebook.GetSelection() == XY_PAGE_INDEX: + options_notebook.SetSelection(0) + #show/hide control buttons + scope_mode_box.ShowItems(not mode) + xy_mode_box.ShowItems(mode) + control_box.Layout() + parent.subscribe(XY_MODE_KEY, xy_mode_changed) + ################################################## + # Channel Menu Boxes + ################################################## + for i in range(parent.num_inputs): + channel_menu_panel = wx.Panel(options_notebook) + options_notebook.AddPage(channel_menu_panel, 'Ch%d'%(i+1)) + channel_menu_box = wx.BoxSizer(wx.VERTICAL) + channel_menu_panel.SetSizer(channel_menu_box) + #ac couple check box + channel_menu_box.AddStretchSpacer() + coupling_chooser = common.DropDownController(channel_menu_panel, COUPLING_MODES, parent, common.index_key(AC_COUPLE_KEY, i), SIZE) + channel_menu_box.Add(common.LabelBox(channel_menu_panel, 'Coupling', coupling_chooser), 0, wx.EXPAND) + #marker + channel_menu_box.AddStretchSpacer() + marker_chooser = common.DropDownController(channel_menu_panel, MARKER_TYPES, parent, common.index_key(MARKER_KEY, i), SIZE) + channel_menu_box.Add(common.LabelBox(channel_menu_panel, 'Marker', marker_chooser), 0, wx.EXPAND) + channel_menu_box.AddStretchSpacer() + ################################################## + # Trigger Menu Box + ################################################## + trigger_menu_panel = wx.Panel(options_notebook) + options_notebook.AddPage(trigger_menu_panel, 'Trig') + trigger_menu_box = wx.BoxSizer(wx.VERTICAL) + trigger_menu_panel.SetSizer(trigger_menu_box) + #trigger mode + trigger_mode_chooser = common.DropDownController(trigger_menu_panel, TRIGGER_MODES, parent, TRIGGER_MODE_KEY, SIZE) + trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Mode', trigger_mode_chooser), 0, wx.EXPAND) + #trigger slope + trigger_slope_chooser = common.DropDownController(trigger_menu_panel, TRIGGER_SLOPES, parent, TRIGGER_SLOPE_KEY, SIZE) + parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_slope_chooser.Enable(x!=gr.gr_TRIG_MODE_FREE)) + trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Slope', trigger_slope_chooser), 0, wx.EXPAND) + #trigger channel + choices = [('Channel %d'%(i+1), i) for i in range(parent.num_inputs)] + trigger_channel_chooser = common.DropDownController(trigger_menu_panel, choices, parent, TRIGGER_CHANNEL_KEY, SIZE) + parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_channel_chooser.Enable(x!=gr.gr_TRIG_MODE_FREE)) + trigger_menu_box.Add(common.LabelBox(trigger_menu_panel, 'Channel', trigger_channel_chooser), 0, wx.EXPAND) + #trigger level + hbox = wx.BoxSizer(wx.HORIZONTAL) + trigger_menu_box.Add(hbox, 0, wx.EXPAND) + hbox.Add(wx.StaticText(trigger_menu_panel, label=' Level '), 1, wx.ALIGN_CENTER_VERTICAL) + trigger_level_button = wx.Button(trigger_menu_panel, label='50%', style=wx.BU_EXACTFIT) + parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_level_button.Enable(x!=gr.gr_TRIG_MODE_FREE)) + trigger_level_button.Bind(wx.EVT_BUTTON, self.parent.set_auto_trigger_level) + hbox.Add(trigger_level_button, 0, wx.ALIGN_CENTER_VERTICAL) + hbox.AddSpacer(10) + trigger_level_buttons = common.IncrDecrButtons(trigger_menu_panel, self._on_incr_trigger_level, self._on_decr_trigger_level) + parent.subscribe(TRIGGER_MODE_KEY, lambda x: trigger_level_buttons.Enable(x!=gr.gr_TRIG_MODE_FREE)) + hbox.Add(trigger_level_buttons, 0, wx.ALIGN_CENTER_VERTICAL) + ################################################## + # XY Menu Box + ################################################## + if parent.num_inputs > 1: + xy_menu_panel = wx.Panel(options_notebook) + options_notebook.AddPage(xy_menu_panel, 'XY') + xy_menu_box = wx.BoxSizer(wx.VERTICAL) + xy_menu_panel.SetSizer(xy_menu_box) + #x and y channel choosers + xy_menu_box.AddStretchSpacer() + choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)] + x_channel_chooser = common.DropDownController(xy_menu_panel, choices, parent, X_CHANNEL_KEY, SIZE) + xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Ch X', x_channel_chooser), 0, wx.EXPAND) + xy_menu_box.AddStretchSpacer() + y_channel_chooser = common.DropDownController(xy_menu_panel, choices, parent, Y_CHANNEL_KEY, SIZE) + xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Ch Y', y_channel_chooser), 0, wx.EXPAND) + #marker + xy_menu_box.AddStretchSpacer() + marker_chooser = common.DropDownController(xy_menu_panel, MARKER_TYPES, parent, XY_MARKER_KEY, SIZE) + xy_menu_box.Add(common.LabelBox(xy_menu_panel, 'Marker', marker_chooser), 0, wx.EXPAND) + xy_menu_box.AddStretchSpacer() + ################################################## + # Run/Stop Button + ################################################## #run/stop self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') control_box.Add(self.run_button, 0, wx.EXPAND) #set sizer self.SetSizerAndFit(control_box) + #mouse wheel event + def on_mouse_wheel(event): + if not parent[XY_MODE_KEY]: + if event.GetWheelRotation() < 0: self._on_incr_t_divs(event) + else: self._on_decr_t_divs(event) + parent.plotter.Bind(wx.EVT_MOUSEWHEEL, on_mouse_wheel) ################################################## # Event handlers ################################################## - def _on_scope_xy_mode(self, mode): - self.scope_mode_box.ShowItems(not mode) - self.xy_mode_box.ShowItems(mode) - self.control_box.Layout() + #trigger level + def _on_incr_trigger_level(self, event): + self.parent[TRIGGER_LEVEL_KEY] += self.parent[Y_PER_DIV_KEY]/3. + def _on_decr_trigger_level(self, event): + self.parent[TRIGGER_LEVEL_KEY] -= self.parent[Y_PER_DIV_KEY]/3. #incr/decr divs def _on_incr_t_divs(self, event): - self.parent.set_t_per_div( - common.get_clean_incr(self.parent[T_PER_DIV_KEY])) + self.parent[T_PER_DIV_KEY] = common.get_clean_incr(self.parent[T_PER_DIV_KEY]) def _on_decr_t_divs(self, event): - self.parent.set_t_per_div( - common.get_clean_decr(self.parent[T_PER_DIV_KEY])) + self.parent[T_PER_DIV_KEY] = common.get_clean_decr(self.parent[T_PER_DIV_KEY]) def _on_incr_x_divs(self, event): - self.parent.set_x_per_div( - common.get_clean_incr(self.parent[X_PER_DIV_KEY])) + self.parent[X_PER_DIV_KEY] = common.get_clean_incr(self.parent[X_PER_DIV_KEY]) def _on_decr_x_divs(self, event): - self.parent.set_x_per_div( - common.get_clean_decr(self.parent[X_PER_DIV_KEY])) + self.parent[X_PER_DIV_KEY] = common.get_clean_decr(self.parent[X_PER_DIV_KEY]) def _on_incr_y_divs(self, event): - self.parent.set_y_per_div( - common.get_clean_incr(self.parent[Y_PER_DIV_KEY])) + self.parent[Y_PER_DIV_KEY] = common.get_clean_incr(self.parent[Y_PER_DIV_KEY]) def _on_decr_y_divs(self, event): - self.parent.set_y_per_div( - common.get_clean_decr(self.parent[Y_PER_DIV_KEY])) + self.parent[Y_PER_DIV_KEY] = common.get_clean_decr(self.parent[Y_PER_DIV_KEY]) #incr/decr offset - def _on_incr_t_off(self, event): - self.parent.set_t_off( - self.parent[T_OFF_KEY] + self.parent[T_PER_DIV_KEY]) - def _on_decr_t_off(self, event): - self.parent.set_t_off( - self.parent[T_OFF_KEY] - self.parent[T_PER_DIV_KEY]) def _on_incr_x_off(self, event): - self.parent.set_x_off( - self.parent[X_OFF_KEY] + self.parent[X_PER_DIV_KEY]) + self.parent[X_OFF_KEY] = self.parent[X_OFF_KEY] + self.parent[X_PER_DIV_KEY] def _on_decr_x_off(self, event): - self.parent.set_x_off( - self.parent[X_OFF_KEY] - self.parent[X_PER_DIV_KEY]) + self.parent[X_OFF_KEY] = self.parent[X_OFF_KEY] - self.parent[X_PER_DIV_KEY] def _on_incr_y_off(self, event): - self.parent.set_y_off( - self.parent[Y_OFF_KEY] + self.parent[Y_PER_DIV_KEY]) + self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] + self.parent[Y_PER_DIV_KEY] def _on_decr_y_off(self, event): - self.parent.set_y_off( - self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY]) + self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY] ################################################## # Scope window with plotter and control panel ################################################## -class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): +class scope_window(wx.Panel, pubsub.pubsub): def __init__( self, parent, @@ -259,11 +296,13 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): sample_rate_key, t_scale, v_scale, - ac_couple, xy_mode, - scope_trigger_level_key, - scope_trigger_mode_key, - scope_trigger_channel_key, + ac_couple_key, + trigger_level_key, + trigger_mode_key, + trigger_slope_key, + trigger_channel_key, + decimation_key, msg_key, ): pubsub.pubsub.__init__(self) @@ -271,67 +310,73 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): assert num_inputs <= len(CHANNEL_COLOR_SPECS) #setup self.sampleses = None - self.ext_controller = controller self.num_inputs = num_inputs - self.sample_rate_key = sample_rate_key - autorange = v_scale is None + autorange = not v_scale self.autorange_ts = 0 - if v_scale is None: v_scale = 1 + v_scale = v_scale or 1 self.frame_rate_ts = 0 - self._init = False #HACK - #scope keys - self.scope_trigger_level_key = scope_trigger_level_key - self.scope_trigger_mode_key = scope_trigger_mode_key - self.scope_trigger_channel_key = scope_trigger_channel_key + #proxy the keys + self.proxy(MSG_KEY, controller, msg_key) + self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) + self.proxy(TRIGGER_LEVEL_KEY, controller, trigger_level_key) + self.proxy(TRIGGER_MODE_KEY, controller, trigger_mode_key) + self.proxy(TRIGGER_SLOPE_KEY, controller, trigger_slope_key) + self.proxy(TRIGGER_CHANNEL_KEY, controller, trigger_channel_key) + self.proxy(DECIMATION_KEY, controller, decimation_key) + for i in range(num_inputs): + self.proxy(common.index_key(AC_COUPLE_KEY, i), controller, common.index_key(ac_couple_key, i)) #init panel and plot - wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.channel_plotter(self) self.plotter.SetSize(wx.Size(*size)) self.plotter.set_title(title) self.plotter.enable_legend(True) self.plotter.enable_point_label(True) + self.plotter.enable_grid_lines(True) #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) - #initial setup - self._register_set_prop(self, RUNNING_KEY, True) - self._register_set_prop(self, AC_COUPLE_KEY, ac_couple) - self._register_set_prop(self, SCOPE_XY_MODE_KEY, xy_mode) - self._register_set_prop(self, AUTORANGE_KEY, autorange) - self._register_set_prop(self, T_PER_DIV_KEY, t_scale) - self._register_set_prop(self, X_PER_DIV_KEY, v_scale) - self._register_set_prop(self, Y_PER_DIV_KEY, v_scale) - self._register_set_prop(self, T_OFF_KEY, 0) - self._register_set_prop(self, X_OFF_KEY, 0) - self._register_set_prop(self, Y_OFF_KEY, 0) - self._register_set_prop(self, T_DIVS_KEY, 8) - self._register_set_prop(self, X_DIVS_KEY, 8) - self._register_set_prop(self, Y_DIVS_KEY, 8) - self._register_set_prop(self, SCOPE_X_CHANNEL_KEY, 0) - self._register_set_prop(self, SCOPE_Y_CHANNEL_KEY, num_inputs-1) - self._register_set_prop(self, FRAME_RATE_KEY, frame_rate) - self._register_set_prop(self, TRIGGER_CHANNEL_KEY, 0) - self._register_set_prop(self, TRIGGER_MODE_KEY, 1) - self._register_set_prop(self, TRIGGER_LEVEL_KEY, None) - self._register_set_prop(self, MARKER_KEY, DEFAULT_MARKER_TYPE) - #register events - self.ext_controller.subscribe(msg_key, self.handle_msg) - for key in ( + #initialize values + self[RUNNING_KEY] = True + for i in range(self.num_inputs): + self[common.index_key(AC_COUPLE_KEY, i)] = self[common.index_key(AC_COUPLE_KEY, i)] + self[common.index_key(MARKER_KEY, i)] = DEFAULT_MARKER_TYPE + self[XY_MARKER_KEY] = 2.0 + self[XY_MODE_KEY] = xy_mode + self[X_CHANNEL_KEY] = 0 + self[Y_CHANNEL_KEY] = self.num_inputs-1 + self[AUTORANGE_KEY] = autorange + self[T_PER_DIV_KEY] = t_scale + self[X_PER_DIV_KEY] = v_scale + self[Y_PER_DIV_KEY] = v_scale + self[T_OFF_KEY] = 0 + self[X_OFF_KEY] = 0 + self[Y_OFF_KEY] = 0 + self[T_DIVS_KEY] = 8 + self[X_DIVS_KEY] = 8 + self[Y_DIVS_KEY] = 8 + self[FRAME_RATE_KEY] = frame_rate + self[TRIGGER_LEVEL_KEY] = 0 + self[TRIGGER_CHANNEL_KEY] = 0 + self[TRIGGER_MODE_KEY] = gr.gr_TRIG_MODE_AUTO + self[TRIGGER_SLOPE_KEY] = gr.gr_TRIG_SLOPE_POS + self[T_FRAC_OFF_KEY] = 0.5 + #register events for message + self.subscribe(MSG_KEY, self.handle_msg) + #register events for grid + for key in [common.index_key(MARKER_KEY, i) for i in range(self.num_inputs)] + [ + TRIGGER_LEVEL_KEY, TRIGGER_MODE_KEY, T_PER_DIV_KEY, X_PER_DIV_KEY, Y_PER_DIV_KEY, T_OFF_KEY, X_OFF_KEY, Y_OFF_KEY, T_DIVS_KEY, X_DIVS_KEY, Y_DIVS_KEY, - SCOPE_XY_MODE_KEY, - SCOPE_X_CHANNEL_KEY, - SCOPE_Y_CHANNEL_KEY, - AUTORANGE_KEY, - AC_COUPLE_KEY, - MARKER_KEY, - ): self.subscribe(key, self.update_grid) - #initial update, dont do this here, wait for handle_msg #HACK - #self.update_grid() + XY_MODE_KEY, AUTORANGE_KEY, T_FRAC_OFF_KEY, + TRIGGER_SHOW_KEY, XY_MARKER_KEY, X_CHANNEL_KEY, Y_CHANNEL_KEY, + ]: self.subscribe(key, self.update_grid) + #initial update + self.update_grid() def handle_msg(self, msg): """ @@ -345,15 +390,23 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): if time.time() - self.frame_rate_ts < 1.0/self[FRAME_RATE_KEY]: return #convert to floating point numbers samples = numpy.fromstring(msg, numpy.float32) + #extract the trigger offset + self.trigger_offset = samples[-1] + samples = samples[:-1] samps_per_ch = len(samples)/self.num_inputs self.sampleses = [samples[samps_per_ch*i:samps_per_ch*(i+1)] for i in range(self.num_inputs)] - if not self._init: #HACK - self._init = True - self.update_grid() #handle samples self.handle_samples() self.frame_rate_ts = time.time() + def set_auto_trigger_level(self, *args): + """ + Use the current trigger channel and samples to calculate the 50% level. + """ + if not self.sampleses: return + samples = self.sampleses[self[TRIGGER_CHANNEL_KEY]] + self[TRIGGER_LEVEL_KEY] = (numpy.max(samples)+numpy.min(samples))/2 + def handle_samples(self): """ Handle the cached samples from the scope input. @@ -361,52 +414,37 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): """ if not self.sampleses: return sampleses = self.sampleses - #trigger level (must do before ac coupling) - self.ext_controller[self.scope_trigger_channel_key] = self[TRIGGER_CHANNEL_KEY] - self.ext_controller[self.scope_trigger_mode_key] = self[TRIGGER_MODE_KEY] - trigger_level = self[TRIGGER_LEVEL_KEY] - if trigger_level is None: self.ext_controller[self.scope_trigger_level_key] = '' - else: - samples = sampleses[self[TRIGGER_CHANNEL_KEY]] - self.ext_controller[self.scope_trigger_level_key] = \ - trigger_level*(numpy.max(samples)-numpy.min(samples))/2 + numpy.average(samples) - #ac coupling - if self[AC_COUPLE_KEY]: - sampleses = [samples - numpy.average(samples) for samples in sampleses] - if self[SCOPE_XY_MODE_KEY]: - x_samples = sampleses[self[SCOPE_X_CHANNEL_KEY]] - y_samples = sampleses[self[SCOPE_Y_CHANNEL_KEY]] + if self[XY_MODE_KEY]: + self[DECIMATION_KEY] = 1 + x_samples = sampleses[self[X_CHANNEL_KEY]] + y_samples = sampleses[self[Y_CHANNEL_KEY]] #autorange if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE: x_min, x_max = common.get_min_max(x_samples) y_min, y_max = common.get_min_max(y_samples) #adjust the x per div x_per_div = common.get_clean_num((x_max-x_min)/self[X_DIVS_KEY]) - if x_per_div != self[X_PER_DIV_KEY]: self.set_x_per_div(x_per_div) + if x_per_div != self[X_PER_DIV_KEY]: self[X_PER_DIV_KEY] = x_per_div; return #adjust the x offset x_off = x_per_div*round((x_max+x_min)/2/x_per_div) - if x_off != self[X_OFF_KEY]: self.set_x_off(x_off) + if x_off != self[X_OFF_KEY]: self[X_OFF_KEY] = x_off; return #adjust the y per div y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY]) - if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div) + if y_per_div != self[Y_PER_DIV_KEY]: self[Y_PER_DIV_KEY] = y_per_div; return #adjust the y offset y_off = y_per_div*round((y_max+y_min)/2/y_per_div) - if y_off != self[Y_OFF_KEY]: self.set_y_off(y_off) + if y_off != self[Y_OFF_KEY]: self[Y_OFF_KEY] = y_off; return self.autorange_ts = time.time() #plot xy channel self.plotter.set_waveform( channel='XY', samples=(x_samples, y_samples), color_spec=CHANNEL_COLOR_SPECS[0], - marker=self[MARKER_KEY], + marker=self[XY_MARKER_KEY], ) #turn off each waveform for i, samples in enumerate(sampleses): - self.plotter.set_waveform( - channel='Ch%d'%(i+1), - samples=[], - color_spec=CHANNEL_COLOR_SPECS[i], - ) + self.plotter.clear_waveform(channel='Ch%d'%(i+1)) else: #autorange if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE: @@ -415,86 +453,89 @@ class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter): y_max = numpy.max([bound[1] for bound in bounds]) #adjust the y per div y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY]) - if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div) + if y_per_div != self[Y_PER_DIV_KEY]: self[Y_PER_DIV_KEY] = y_per_div; return #adjust the y offset y_off = y_per_div*round((y_max+y_min)/2/y_per_div) - if y_off != self[Y_OFF_KEY]: self.set_y_off(y_off) + if y_off != self[Y_OFF_KEY]: self[Y_OFF_KEY] = y_off; return self.autorange_ts = time.time() - #plot each waveform - for i, samples in enumerate(sampleses): - #number of samples to scale to the screen - num_samps = int(self[T_PER_DIV_KEY]*self[T_DIVS_KEY]*self.ext_controller[self.sample_rate_key]) - #handle num samps out of bounds - if num_samps > len(samples): - self.set_t_per_div( - common.get_clean_decr(self[T_PER_DIV_KEY])) - elif num_samps < 2: - self.set_t_per_div( - common.get_clean_incr(self[T_PER_DIV_KEY])) - num_samps = 0 - else: + #number of samples to scale to the screen + actual_rate = self.get_actual_rate() + time_span = self[T_PER_DIV_KEY]*self[T_DIVS_KEY] + num_samps = int(round(time_span*actual_rate)) + #handle the time offset + t_off = self[T_FRAC_OFF_KEY]*(len(sampleses[0])/actual_rate - time_span) + if t_off != self[T_OFF_KEY]: self[T_OFF_KEY] = t_off; return + samps_off = int(round(actual_rate*self[T_OFF_KEY])) + #adjust the decim so that we use about half the samps + self[DECIMATION_KEY] = int(round( + time_span*self[SAMPLE_RATE_KEY]/(0.5*len(sampleses[0])) + ) + ) + #num samps too small, auto increment the time + if num_samps < 2: self[T_PER_DIV_KEY] = common.get_clean_incr(self[T_PER_DIV_KEY]) + #num samps in bounds, plot each waveform + elif num_samps <= len(sampleses[0]): + for i, samples in enumerate(sampleses): #plot samples self.plotter.set_waveform( channel='Ch%d'%(i+1), - samples=samples[:num_samps], + samples=samples[samps_off:num_samps+samps_off], color_spec=CHANNEL_COLOR_SPECS[i], - marker=self[MARKER_KEY], + marker=self[common.index_key(MARKER_KEY, i)], + trig_off=self.trigger_offset, ) #turn XY channel off + self.plotter.clear_waveform(channel='XY') + #keep trigger level within range + if self[TRIGGER_LEVEL_KEY] > self.get_y_max(): + self[TRIGGER_LEVEL_KEY] = self.get_y_max(); return + if self[TRIGGER_LEVEL_KEY] < self.get_y_min(): + self[TRIGGER_LEVEL_KEY] = self.get_y_min(); return + #disable the trigger channel + if not self[TRIGGER_SHOW_KEY] or self[XY_MODE_KEY] or self[TRIGGER_MODE_KEY] == gr.gr_TRIG_MODE_FREE: + self.plotter.clear_waveform(channel='Trig') + else: #show trigger channel + trigger_level = self[TRIGGER_LEVEL_KEY] + trigger_point = (len(self.sampleses[0])-1)/self.get_actual_rate()/2.0 self.plotter.set_waveform( - channel='XY', - samples=[], - color_spec=CHANNEL_COLOR_SPECS[0], + channel='Trig', + samples=( + [self.get_t_min(), trigger_point, trigger_point, trigger_point, trigger_point, self.get_t_max()], + [trigger_level, trigger_level, self.get_y_max(), self.get_y_min(), trigger_level, trigger_level] + ), + color_spec=TRIGGER_COLOR_SPEC, ) #update the plotter self.plotter.update() + def get_actual_rate(self): return 1.0*self[SAMPLE_RATE_KEY]/self[DECIMATION_KEY] + def get_t_min(self): return self[T_OFF_KEY] + def get_t_max(self): return self[T_PER_DIV_KEY]*self[T_DIVS_KEY] + self[T_OFF_KEY] + def get_x_min(self): return -1*self[X_PER_DIV_KEY]*self[X_DIVS_KEY]/2.0 + self[X_OFF_KEY] + def get_x_max(self): return self[X_PER_DIV_KEY]*self[X_DIVS_KEY]/2.0 + self[X_OFF_KEY] + def get_y_min(self): return -1*self[Y_PER_DIV_KEY]*self[Y_DIVS_KEY]/2.0 + self[Y_OFF_KEY] + def get_y_max(self): return self[Y_PER_DIV_KEY]*self[Y_DIVS_KEY]/2.0 + self[Y_OFF_KEY] + def update_grid(self, *args): """ Update the grid to reflect the current settings: xy divisions, xy offset, xy mode setting """ - #grid parameters - t_per_div = self[T_PER_DIV_KEY] - x_per_div = self[X_PER_DIV_KEY] - y_per_div = self[Y_PER_DIV_KEY] - t_off = self[T_OFF_KEY] - x_off = self[X_OFF_KEY] - y_off = self[Y_OFF_KEY] - t_divs = self[T_DIVS_KEY] - x_divs = self[X_DIVS_KEY] - y_divs = self[Y_DIVS_KEY] - if self[SCOPE_XY_MODE_KEY]: + if self[T_FRAC_OFF_KEY] < 0: self[T_FRAC_OFF_KEY] = 0; return + if self[T_FRAC_OFF_KEY] > 1: self[T_FRAC_OFF_KEY] = 1; return + if self[XY_MODE_KEY]: #update the x axis - self.plotter.set_x_label('Ch%d'%(self[SCOPE_X_CHANNEL_KEY]+1)) - self.plotter.set_x_grid( - -1*x_per_div*x_divs/2.0 + x_off, - x_per_div*x_divs/2.0 + x_off, - x_per_div, - ) + self.plotter.set_x_label('Ch%d'%(self[X_CHANNEL_KEY]+1)) + self.plotter.set_x_grid(self.get_x_min(), self.get_x_max(), self[X_PER_DIV_KEY]) #update the y axis - self.plotter.set_y_label('Ch%d'%(self[SCOPE_Y_CHANNEL_KEY]+1)) - self.plotter.set_y_grid( - -1*y_per_div*y_divs/2.0 + y_off, - y_per_div*y_divs/2.0 + y_off, - y_per_div, - ) + self.plotter.set_y_label('Ch%d'%(self[Y_CHANNEL_KEY]+1)) + self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY]) else: #update the t axis - coeff, exp, prefix = common.get_si_components(t_per_div*t_divs + t_off) - self.plotter.set_x_label('Time', prefix+'s') - self.plotter.set_x_grid( - t_off, - t_per_div*t_divs + t_off, - t_per_div, - 10**(-exp), - ) + self.plotter.set_x_label('Time', 's') + self.plotter.set_x_grid(self.get_t_min(), self.get_t_max(), self[T_PER_DIV_KEY], True) #update the y axis self.plotter.set_y_label('Counts') - self.plotter.set_y_grid( - -1*y_per_div*y_divs/2.0 + y_off, - y_per_div*y_divs/2.0 + y_off, - y_per_div, - ) + self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY]) #redraw current sample self.handle_samples() diff --git a/gr-wxgui/src/python/scopesink2.py b/gr-wxgui/src/python/scopesink2.py index 40f9f388..87aa4337 100644 --- a/gr-wxgui/src/python/scopesink2.py +++ b/gr-wxgui/src/python/scopesink2.py @@ -38,7 +38,7 @@ if style == 'gl': except ImportError: raise RuntimeError("Unable to import OpenGL. Are Python wrappers for OpenGL installed?") - from scopesink_gl import scope_sink_f, scope_sink_c, constellation_sink + from scopesink_gl import scope_sink_f, scope_sink_c else: - from scopesink_nongl import scope_sink_f, scope_sink_c, constellation_sink + from scopesink_nongl import scope_sink_f, scope_sink_c diff --git a/gr-wxgui/src/python/scopesink_gl.py b/gr-wxgui/src/python/scopesink_gl.py index 36d8d8b8..73125c35 100644 --- a/gr-wxgui/src/python/scopesink_gl.py +++ b/gr-wxgui/src/python/scopesink_gl.py @@ -28,10 +28,38 @@ from gnuradio import gr from pubsub import pubsub from constants import * +class ac_couple_block(gr.hier_block2): + """ + AC couple the incoming stream by subtracting out the low pass signal. + Mute the low pass filter to disable ac coupling. + """ + + def __init__(self, controller, ac_couple_key, ac_couple, sample_rate_key): + gr.hier_block2.__init__( + self, + "ac_couple", + gr.io_signature(1, 1, gr.sizeof_float), + gr.io_signature(1, 1, gr.sizeof_float), + ) + #blocks + copy = gr.kludge_copy(gr.sizeof_float) + lpf = gr.single_pole_iir_filter_ff(0.0) + sub = gr.sub_ff() + mute = gr.mute_ff() + #connect + self.connect(self, copy, sub, self) + self.connect(copy, lpf, mute, (sub, 1)) + #subscribe + controller.subscribe(ac_couple_key, lambda x: mute.set_mute(not x)) + controller.subscribe(sample_rate_key, lambda x: lpf.set_taps(2.0/x)) + #initialize + controller[ac_couple_key] = ac_couple + controller[sample_rate_key] = controller[sample_rate_key] + ################################################## # Scope sink block (wrapper for old wxgui) ################################################## -class _scope_sink_base(gr.hier_block2, common.prop_setter): +class _scope_sink_base(gr.hier_block2): """ A scope block with a gui window. """ @@ -42,15 +70,14 @@ class _scope_sink_base(gr.hier_block2, common.prop_setter): title='', sample_rate=1, size=scope_window.DEFAULT_WIN_SIZE, - frame_decim=None, #ignore (old wrapper) - v_scale=scope_window.DEFAULT_V_SCALE, - t_scale=None, - num_inputs=1, - ac_couple=False, + v_scale=0, + t_scale=0, xy_mode=False, + ac_couple=False, + num_inputs=1, frame_rate=scope_window.DEFAULT_FRAME_RATE, ): - if t_scale is None: t_scale = 0.001 + if not t_scale: t_scale = 10.0/sample_rate #init gr.hier_block2.__init__( self, @@ -61,37 +88,41 @@ class _scope_sink_base(gr.hier_block2, common.prop_setter): #scope msgq = gr.msg_queue(2) scope = gr.oscope_sink_f(sample_rate, msgq) + #controller + self.controller = pubsub() + self.controller.subscribe(SAMPLE_RATE_KEY, scope.set_sample_rate) + self.controller.publish(SAMPLE_RATE_KEY, scope.sample_rate) + self.controller.subscribe(DECIMATION_KEY, scope.set_decimation_count) + self.controller.publish(DECIMATION_KEY, scope.get_decimation_count) + self.controller.subscribe(TRIGGER_LEVEL_KEY, scope.set_trigger_level) + self.controller.publish(TRIGGER_LEVEL_KEY, scope.get_trigger_level) + self.controller.subscribe(TRIGGER_MODE_KEY, scope.set_trigger_mode) + self.controller.publish(TRIGGER_MODE_KEY, scope.get_trigger_mode) + self.controller.subscribe(TRIGGER_SLOPE_KEY, scope.set_trigger_slope) + self.controller.publish(TRIGGER_SLOPE_KEY, scope.get_trigger_slope) + self.controller.subscribe(TRIGGER_CHANNEL_KEY, scope.set_trigger_channel) + self.controller.publish(TRIGGER_CHANNEL_KEY, scope.get_trigger_channel) #connect if self._real: for i in range(num_inputs): - self.connect((self, i), (scope, i)) + self.connect( + (self, i), + ac_couple_block(self.controller, common.index_key(AC_COUPLE_KEY, i), ac_couple, SAMPLE_RATE_KEY), + (scope, i), + ) else: for i in range(num_inputs): c2f = gr.complex_to_float() self.connect((self, i), c2f) - self.connect((c2f, 0), (scope, 2*i+0)) - self.connect((c2f, 1), (scope, 2*i+1)) + for j in range(2): + self.connect( + (c2f, j), + ac_couple_block(self.controller, common.index_key(AC_COUPLE_KEY, 2*i+j), ac_couple, SAMPLE_RATE_KEY), + (scope, 2*i+j), + ) num_inputs *= 2 - #controller - self.controller = pubsub() - self.controller.subscribe(SAMPLE_RATE_KEY, scope.set_sample_rate) - self.controller.publish(SAMPLE_RATE_KEY, scope.sample_rate) - def set_trigger_level(level): - if level == '': scope.set_trigger_level_auto() - else: scope.set_trigger_level(level) - self.controller.subscribe(SCOPE_TRIGGER_LEVEL_KEY, set_trigger_level) - def set_trigger_mode(mode): - if mode == 0: mode = gr.gr_TRIG_AUTO - elif mode < 0: mode = gr.gr_TRIG_NEG_SLOPE - elif mode > 0: mode = gr.gr_TRIG_POS_SLOPE - else: return - scope.set_trigger_mode(mode) - self.controller.subscribe(SCOPE_TRIGGER_MODE_KEY, set_trigger_mode) - self.controller.subscribe(SCOPE_TRIGGER_CHANNEL_KEY, scope.set_trigger_channel) #start input watcher - def setter(p, k, x): # lambdas can't have assignments :( - p[k] = x - common.input_watcher(msgq, lambda x: setter(self.controller, MSG_KEY, x)) + common.input_watcher(msgq, self.controller, MSG_KEY) #create window self.win = scope_window.scope_window( parent=parent, @@ -103,21 +134,16 @@ class _scope_sink_base(gr.hier_block2, common.prop_setter): sample_rate_key=SAMPLE_RATE_KEY, t_scale=t_scale, v_scale=v_scale, - ac_couple=ac_couple, xy_mode=xy_mode, - scope_trigger_level_key=SCOPE_TRIGGER_LEVEL_KEY, - scope_trigger_mode_key=SCOPE_TRIGGER_MODE_KEY, - scope_trigger_channel_key=SCOPE_TRIGGER_CHANNEL_KEY, + ac_couple_key=AC_COUPLE_KEY, + trigger_level_key=TRIGGER_LEVEL_KEY, + trigger_mode_key=TRIGGER_MODE_KEY, + trigger_slope_key=TRIGGER_SLOPE_KEY, + trigger_channel_key=TRIGGER_CHANNEL_KEY, + decimation_key=DECIMATION_KEY, msg_key=MSG_KEY, ) - #register callbacks from window for external use - for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): - setattr(self, attr, getattr(self.win, attr)) - self._register_set_prop(self.controller, SAMPLE_RATE_KEY) - #backwards compadibility - self.win.set_format_line = lambda: setter(self.win, MARKER_KEY, None) - self.win.set_format_dot = lambda: setter(self.win, MARKER_KEY, 2.0) - self.win.set_format_plus = lambda: setter(self.win, MARKER_KEY, 3.0) + common.register_access_methods(self, self.win) class scope_sink_f(_scope_sink_base): _item_size = gr.sizeof_float @@ -127,12 +153,6 @@ class scope_sink_c(_scope_sink_base): _item_size = gr.sizeof_gr_complex _real = False -#backwards compadible wrapper (maybe only grc uses this) -class constellation_sink(scope_sink_c): - def __init__(self, *args, **kwargs): - scope_sink_c.__init__(self, *args, **kwargs) - self.set_scope_xy_mode(True) - # ---------------------------------------------------------------- # Stand-alone test application # ---------------------------------------------------------------- @@ -171,7 +191,6 @@ class test_top_block (stdgui2.std_top_block): self.thr = gr.throttle(gr.sizeof_gr_complex, input_rate) scope = scope_sink_c (panel,"Secret Data",sample_rate=input_rate, - frame_decim=frame_decim, v_scale=v_scale, t_scale=t_scale) vbox.Add (scope.win, 1, wx.EXPAND) diff --git a/gr-wxgui/src/python/scopesink_nongl.py b/gr-wxgui/src/python/scopesink_nongl.py index 71fd7e12..34967dd1 100644 --- a/gr-wxgui/src/python/scopesink_nongl.py +++ b/gr-wxgui/src/python/scopesink_nongl.py @@ -35,7 +35,7 @@ default_frame_decim = gr.prefs().get_long('wxgui', 'frame_decim', 1) class scope_sink_f(gr.hier_block2): def __init__(self, parent, title='', sample_rate=1, size=default_scopesink_size, frame_decim=default_frame_decim, - v_scale=default_v_scale, t_scale=None, num_inputs=1): + v_scale=default_v_scale, t_scale=None, num_inputs=1, **kwargs): gr.hier_block2.__init__(self, "scope_sink_f", gr.io_signature(num_inputs, num_inputs, gr.sizeof_float), @@ -56,7 +56,7 @@ class scope_sink_f(gr.hier_block2): class scope_sink_c(gr.hier_block2): def __init__(self, parent, title='', sample_rate=1, size=default_scopesink_size, frame_decim=default_frame_decim, - v_scale=default_v_scale, t_scale=None, num_inputs=1): + v_scale=default_v_scale, t_scale=None, num_inputs=1, **kwargs): gr.hier_block2.__init__(self, "scope_sink_c", gr.io_signature(num_inputs, num_inputs, gr.sizeof_gr_complex), @@ -167,10 +167,7 @@ class win_info (object): self.marker = 'line' self.xy = xy - if v_scale == None: # 0 and None are both False, but 0 != None - self.autorange = True - else: - self.autorange = False # 0 is a valid v_scale + self.autorange = not v_scale self.running = True def get_time_per_div (self): @@ -320,7 +317,8 @@ class scope_window (wx.Panel): ctrlbox.Add (self.trig_chan_choice, 0, wx.ALIGN_CENTER) self.trig_mode_choice = wx.Choice (self, 1005, - choices = ['Auto', 'Pos', 'Neg']) + choices = ['Free', 'Auto', 'Norm']) + self.trig_mode_choice.SetSelection(1) self.trig_mode_choice.SetToolTipString ("Select trigger slope or Auto (untriggered roll)") wx.EVT_CHOICE (self, 1005, self.trig_mode_choice_event) ctrlbox.Add (self.trig_mode_choice, 0, wx.ALIGN_CENTER) @@ -432,12 +430,12 @@ class scope_window (wx.Panel): def trig_mode_choice_event (self, evt): sink = self.info.scopesink s = evt.GetString () - if s == 'Pos': - sink.set_trigger_mode (gr.gr_TRIG_POS_SLOPE) - elif s == 'Neg': - sink.set_trigger_mode (gr.gr_TRIG_NEG_SLOPE) + if s == 'Norm': + sink.set_trigger_mode (gr.gr_TRIG_MODE_NORM) elif s == 'Auto': - sink.set_trigger_mode (gr.gr_TRIG_AUTO) + sink.set_trigger_mode (gr.gr_TRIG_MODE_AUTO) + elif s == 'Free': + sink.set_trigger_mode (gr.gr_TRIG_MODE_FREE) else: assert 0, "Bad trig_mode_choice string" diff --git a/gr-wxgui/src/python/waterfall_window.py b/gr-wxgui/src/python/waterfall_window.py index f24b142a..8dcb4b61 100644 --- a/gr-wxgui/src/python/waterfall_window.py +++ b/gr-wxgui/src/python/waterfall_window.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU General Public License # along with GNU Radio; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, -# Boston, MA 02110-1301, USA. +# Boston, MA 02110-1`301, USA. # ################################################## @@ -61,57 +61,57 @@ class control_panel(wx.Panel): @param parent the wx parent window """ self.parent = parent - wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER) + wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER) control_box = wx.BoxSizer(wx.VERTICAL) control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER) #color mode control_box.AddStretchSpacer() - self.color_mode_chooser = common.DropDownController(self, 'Color', COLOR_MODES, parent, COLOR_MODE_KEY) - control_box.Add(self.color_mode_chooser, 0, wx.EXPAND) + color_mode_chooser = common.DropDownController(self, COLOR_MODES, parent, COLOR_MODE_KEY) + control_box.Add(common.LabelBox(self, 'Color', color_mode_chooser), 0, wx.EXPAND) #average control_box.AddStretchSpacer() - self.average_check_box = common.CheckBoxController(self, 'Average', parent.ext_controller, parent.average_key) - control_box.Add(self.average_check_box, 0, wx.EXPAND) + average_check_box = common.CheckBoxController(self, 'Average', parent, AVERAGE_KEY) + control_box.Add(average_check_box, 0, wx.EXPAND) control_box.AddSpacer(2) - self.avg_alpha_slider = common.LogSliderController( + avg_alpha_slider = common.LogSliderController( self, 'Avg Alpha', AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS, - parent.ext_controller, parent.avg_alpha_key, + parent, AVG_ALPHA_KEY, formatter=lambda x: ': %.4f'%x, ) - parent.ext_controller.subscribe(parent.average_key, self.avg_alpha_slider.Enable) - control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND) + parent.subscribe(AVERAGE_KEY, avg_alpha_slider.Enable) + control_box.Add(avg_alpha_slider, 0, wx.EXPAND) #dyanmic range buttons control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Dynamic Range'), 0, wx.ALIGN_CENTER) control_box.AddSpacer(2) - self._dynamic_range_buttons = common.IncrDecrButtons(self, self._on_incr_dynamic_range, self._on_decr_dynamic_range) - control_box.Add(self._dynamic_range_buttons, 0, wx.ALIGN_CENTER) + dynamic_range_buttons = common.IncrDecrButtons(self, self._on_incr_dynamic_range, self._on_decr_dynamic_range) + control_box.Add(dynamic_range_buttons, 0, wx.ALIGN_CENTER) #ref lvl buttons control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Set Ref Level'), 0, wx.ALIGN_CENTER) control_box.AddSpacer(2) - self._ref_lvl_buttons = common.IncrDecrButtons(self, self._on_incr_ref_level, self._on_decr_ref_level) - control_box.Add(self._ref_lvl_buttons, 0, wx.ALIGN_CENTER) + ref_lvl_buttons = common.IncrDecrButtons(self, self._on_incr_ref_level, self._on_decr_ref_level) + control_box.Add(ref_lvl_buttons, 0, wx.ALIGN_CENTER) #num lines buttons control_box.AddStretchSpacer() control_box.Add(common.LabelText(self, 'Set Time Scale'), 0, wx.ALIGN_CENTER) control_box.AddSpacer(2) - self._time_scale_buttons = common.IncrDecrButtons(self, self._on_incr_time_scale, self._on_decr_time_scale) - control_box.Add(self._time_scale_buttons, 0, wx.ALIGN_CENTER) + time_scale_buttons = common.IncrDecrButtons(self, self._on_incr_time_scale, self._on_decr_time_scale) + control_box.Add(time_scale_buttons, 0, wx.ALIGN_CENTER) #autoscale control_box.AddStretchSpacer() - self.autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) - self.autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) - control_box.Add(self.autoscale_button, 0, wx.EXPAND) + autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT) + autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale) + control_box.Add(autoscale_button, 0, wx.EXPAND) #clear - self.clear_button = wx.Button(self, label='Clear', style=wx.BU_EXACTFIT) - self.clear_button.Bind(wx.EVT_BUTTON, self._on_clear_button) - control_box.Add(self.clear_button, 0, wx.EXPAND) + clear_button = wx.Button(self, label='Clear', style=wx.BU_EXACTFIT) + clear_button.Bind(wx.EVT_BUTTON, self._on_clear_button) + control_box.Add(clear_button, 0, wx.EXPAND) #run/stop - self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') - control_box.Add(self.run_button, 0, wx.EXPAND) + run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run') + control_box.Add(run_button, 0, wx.EXPAND) #set sizer self.SetSizerAndFit(control_box) @@ -119,34 +119,30 @@ class control_panel(wx.Panel): # Event handlers ################################################## def _on_clear_button(self, event): - self.parent.set_num_lines(self.parent[NUM_LINES_KEY]) + self.parent[NUM_LINES_KEY] = self.parent[NUM_LINES_KEY] def _on_incr_dynamic_range(self, event): - self.parent.set_dynamic_range( - min(self.parent[DYNAMIC_RANGE_KEY] + 10, MAX_DYNAMIC_RANGE)) + self.parent[DYNAMIC_RANGE_KEY] = min(self.parent[DYNAMIC_RANGE_KEY] + 10, MAX_DYNAMIC_RANGE) def _on_decr_dynamic_range(self, event): - self.parent.set_dynamic_range( - max(self.parent[DYNAMIC_RANGE_KEY] - 10, MIN_DYNAMIC_RANGE)) + self.parent[DYNAMIC_RANGE_KEY] = max(self.parent[DYNAMIC_RANGE_KEY] - 10, MIN_DYNAMIC_RANGE) def _on_incr_ref_level(self, event): - self.parent.set_ref_level( - self.parent[REF_LEVEL_KEY] + self.parent[DYNAMIC_RANGE_KEY]*.1) + self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] + self.parent[DYNAMIC_RANGE_KEY]*.1 def _on_decr_ref_level(self, event): - self.parent.set_ref_level( - self.parent[REF_LEVEL_KEY] - self.parent[DYNAMIC_RANGE_KEY]*.1) + self.parent[REF_LEVEL_KEY] = self.parent[REF_LEVEL_KEY] - self.parent[DYNAMIC_RANGE_KEY]*.1 def _on_incr_time_scale(self, event): - old_rate = self.parent.ext_controller[self.parent.frame_rate_key] - self.parent.ext_controller[self.parent.frame_rate_key] *= 0.75 - if self.parent.ext_controller[self.parent.frame_rate_key] == old_rate: - self.parent.ext_controller[self.parent.decimation_key] += 1 + old_rate = self.parent[FRAME_RATE_KEY] + self.parent[FRAME_RATE_KEY] *= 0.75 + if self.parent[FRAME_RATE_KEY] == old_rate: + self.parent[DECIMATION_KEY] += 1 def _on_decr_time_scale(self, event): - old_rate = self.parent.ext_controller[self.parent.frame_rate_key] - self.parent.ext_controller[self.parent.frame_rate_key] *= 1.25 - if self.parent.ext_controller[self.parent.frame_rate_key] == old_rate: - self.parent.ext_controller[self.parent.decimation_key] -= 1 + old_rate = self.parent[FRAME_RATE_KEY] + self.parent[FRAME_RATE_KEY] *= 1.25 + if self.parent[FRAME_RATE_KEY] == old_rate: + self.parent[DECIMATION_KEY] -= 1 ################################################## # Waterfall window with plotter and control panel ################################################## -class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): +class waterfall_window(wx.Panel, pubsub.pubsub): def __init__( self, parent, @@ -169,20 +165,22 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): pubsub.pubsub.__init__(self) #setup self.samples = list() - self.ext_controller = controller self.real = real self.fft_size = fft_size - self.decimation_key = decimation_key - self.sample_rate_key = sample_rate_key - self.frame_rate_key = frame_rate_key - self.average_key = average_key - self.avg_alpha_key = avg_alpha_key + #proxy the keys + self.proxy(MSG_KEY, controller, msg_key) + self.proxy(DECIMATION_KEY, controller, decimation_key) + self.proxy(FRAME_RATE_KEY, controller, frame_rate_key) + self.proxy(AVERAGE_KEY, controller, average_key) + self.proxy(AVG_ALPHA_KEY, controller, avg_alpha_key) + self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key) #init panel and plot - wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER) + wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER) self.plotter = plotter.waterfall_plotter(self) self.plotter.SetSize(wx.Size(*size)) self.plotter.set_title(title) self.plotter.enable_point_label(True) + self.plotter.enable_grid_lines(False) #setup the box with plot and controls self.control_panel = control_panel(self) main_box = wx.BoxSizer(wx.HORIZONTAL) @@ -192,26 +190,23 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): #plotter listeners self.subscribe(COLOR_MODE_KEY, self.plotter.set_color_mode) self.subscribe(NUM_LINES_KEY, self.plotter.set_num_lines) - #initial setup - self.ext_controller[self.average_key] = self.ext_controller[self.average_key] - self.ext_controller[self.avg_alpha_key] = self.ext_controller[self.avg_alpha_key] - self._register_set_prop(self, DYNAMIC_RANGE_KEY, dynamic_range) - self._register_set_prop(self, NUM_LINES_KEY, num_lines) - self._register_set_prop(self, Y_DIVS_KEY, 8) - self._register_set_prop(self, X_DIVS_KEY, 8) #approximate - self._register_set_prop(self, REF_LEVEL_KEY, ref_level) - self._register_set_prop(self, BASEBAND_FREQ_KEY, baseband_freq) - self._register_set_prop(self, COLOR_MODE_KEY, COLOR_MODES[0][1]) - self._register_set_prop(self, RUNNING_KEY, True) + #initialize values + self[AVERAGE_KEY] = self[AVERAGE_KEY] + self[AVG_ALPHA_KEY] = self[AVG_ALPHA_KEY] + self[DYNAMIC_RANGE_KEY] = dynamic_range + self[NUM_LINES_KEY] = num_lines + self[Y_DIVS_KEY] = 8 + self[X_DIVS_KEY] = 8 #approximate + self[REF_LEVEL_KEY] = ref_level + self[BASEBAND_FREQ_KEY] = baseband_freq + self[COLOR_MODE_KEY] = COLOR_MODES[0][1] + self[RUNNING_KEY] = True #register events - self.ext_controller.subscribe(msg_key, self.handle_msg) - self.ext_controller.subscribe(self.decimation_key, self.update_grid) - self.ext_controller.subscribe(self.sample_rate_key, self.update_grid) - self.ext_controller.subscribe(self.frame_rate_key, self.update_grid) - self.subscribe(BASEBAND_FREQ_KEY, self.update_grid) - self.subscribe(NUM_LINES_KEY, self.update_grid) - self.subscribe(Y_DIVS_KEY, self.update_grid) - self.subscribe(X_DIVS_KEY, self.update_grid) + self.subscribe(MSG_KEY, self.handle_msg) + for key in ( + DECIMATION_KEY, SAMPLE_RATE_KEY, FRAME_RATE_KEY, + BASEBAND_FREQ_KEY, X_DIVS_KEY, Y_DIVS_KEY, NUM_LINES_KEY, + ): self.subscribe(key, self.update_grid) #initial update self.update_grid() @@ -230,8 +225,8 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): noise_floor -= abs(noise_floor)*.5 peak_level += abs(peak_level)*.1 #set the range and level - self.set_ref_level(peak_level) - self.set_dynamic_range(peak_level - noise_floor) + self[REF_LEVEL_KEY] = peak_level + self[DYNAMIC_RANGE_KEY] = peak_level - noise_floor def handle_msg(self, msg): """ @@ -266,8 +261,8 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): The y axis depends on y per div, y divs, and ref level. """ #grid parameters - sample_rate = self.ext_controller[self.sample_rate_key] - frame_rate = self.ext_controller[self.frame_rate_key] + sample_rate = self[SAMPLE_RATE_KEY] + frame_rate = self[FRAME_RATE_KEY] baseband_freq = self[BASEBAND_FREQ_KEY] num_lines = self[NUM_LINES_KEY] y_divs = self[Y_DIVS_KEY] @@ -276,28 +271,25 @@ class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter): if self.real: x_width = sample_rate/2.0 else: x_width = sample_rate/1.0 x_per_div = common.get_clean_num(x_width/x_divs) - coeff, exp, prefix = common.get_si_components(abs(baseband_freq) + abs(sample_rate/2.0)) #update the x grid if self.real: self.plotter.set_x_grid( baseband_freq, baseband_freq + sample_rate/2.0, - x_per_div, - 10**(-exp), + x_per_div, True, ) else: self.plotter.set_x_grid( baseband_freq - sample_rate/2.0, baseband_freq + sample_rate/2.0, - x_per_div, - 10**(-exp), + x_per_div, True, ) #update x units - self.plotter.set_x_label('Frequency', prefix+'Hz') + self.plotter.set_x_label('Frequency', 'Hz') #update y grid duration = float(num_lines)/frame_rate y_per_div = common.get_clean_num(duration/y_divs) - self.plotter.set_y_grid(0, duration, y_per_div) + self.plotter.set_y_grid(0, duration, y_per_div, True) #update y units self.plotter.set_y_label('Time', 's') #update plotter diff --git a/gr-wxgui/src/python/waterfallsink_gl.py b/gr-wxgui/src/python/waterfallsink_gl.py index dd8e4575..344640af 100644 --- a/gr-wxgui/src/python/waterfallsink_gl.py +++ b/gr-wxgui/src/python/waterfallsink_gl.py @@ -31,7 +31,7 @@ from constants import * ################################################## # Waterfall sink block (wrapper for old wxgui) ################################################## -class _waterfall_sink_base(gr.hier_block2, common.prop_setter): +class _waterfall_sink_base(gr.hier_block2): """ An fft block with real/complex inputs and a gui window. """ @@ -89,9 +89,7 @@ class _waterfall_sink_base(gr.hier_block2, common.prop_setter): self.controller.subscribe(FRAME_RATE_KEY, fft.set_vec_rate) self.controller.publish(FRAME_RATE_KEY, fft.frame_rate) #start input watcher - def setter(p, k, x): # lambdas can't have assignments :( - p[k] = x - common.input_watcher(msgq, lambda x: setter(self.controller, MSG_KEY, x)) + common.input_watcher(msgq, self.controller, MSG_KEY) #create window self.win = waterfall_window.waterfall_window( parent=parent, @@ -111,12 +109,8 @@ class _waterfall_sink_base(gr.hier_block2, common.prop_setter): avg_alpha_key=AVG_ALPHA_KEY, msg_key=MSG_KEY, ) - #register callbacks from window for external use - for attr in filter(lambda a: a.startswith('set_'), dir(self.win)): - setattr(self, attr, getattr(self.win, attr)) - self._register_set_prop(self.controller, SAMPLE_RATE_KEY) - self._register_set_prop(self.controller, AVERAGE_KEY) - self._register_set_prop(self.controller, AVG_ALPHA_KEY) + common.register_access_methods(self, self.win) + setattr(self.win, 'set_baseband_freq', getattr(self, 'set_baseband_freq')) #BACKWARDS class waterfall_sink_f(_waterfall_sink_base): _fft_chain = blks2.logpwrfft_f diff --git a/grc/data/platforms/python/block_tree.xml b/grc/data/platforms/python/block_tree.xml index bb2d681f..7e13aaff 100644 --- a/grc/data/platforms/python/block_tree.xml +++ b/grc/data/platforms/python/block_tree.xml @@ -38,6 +38,7 @@ wxgui_fftsink2 wxgui_constellationsink2 wxgui_waterfallsink2 + wxgui_histosink2 Operators diff --git a/grc/data/platforms/python/blocks/Makefile.am b/grc/data/platforms/python/blocks/Makefile.am index 6b09050f..506648d4 100644 --- a/grc/data/platforms/python/blocks/Makefile.am +++ b/grc/data/platforms/python/blocks/Makefile.am @@ -210,6 +210,7 @@ dist_ourdata_DATA = \ variable_text_box.xml \ wxgui_constellationsink2.xml \ wxgui_fftsink2.xml \ + wxgui_histosink2.xml \ wxgui_numbersink2.xml \ wxgui_scopesink2.xml \ wxgui_waterfallsink2.xml \ diff --git a/grc/data/platforms/python/blocks/wxgui_constellationsink2.xml b/grc/data/platforms/python/blocks/wxgui_constellationsink2.xml index cf3f2928..69869904 100644 --- a/grc/data/platforms/python/blocks/wxgui_constellationsink2.xml +++ b/grc/data/platforms/python/blocks/wxgui_constellationsink2.xml @@ -7,14 +7,22 @@ Constellation Sink wxgui_constellationsink2 - from gnuradio.wxgui import scopesink2 - scopesink2.constellation_sink( + from gnuradio.wxgui import constsink_gl + constsink_gl.const_sink_c( self.GetWin(), title=$title, sample_rate=$samp_rate, - frame_decim=$frame_decim, + frame_rate=$frame_rate, + const_size=$const_size, + M=$M, + theta=$theta, + alpha=$alpha, + fmax=$fmax, + mu=$mu, + gain_mu=$gain_mu, + symbol_rate=$symbol_rate, + omega_limit=$omega_limit, ) -self.$(id).win.$(marker)() #set $grid_pos = $grid_pos.eval #if not grid_pos self.Add(self.$(id).win) @@ -35,28 +43,64 @@ self.GridAdd(self.$(id).win, $grid_pos[0], $grid_pos[1], $grid_pos[2], $grid_pos real - Frame Decimation - frame_decim - 15 + Frame Rate + frame_rate + 5 + real + + + Constellation Size + const_size + 2048 + real + + + M + M + 4 int - Marker - marker - set_format_plus - enum - - - + Theta + theta + 0 + real + + + Alpha + alpha + 0.005 + real + + + Max Freq + fmax + 0.06 + real + + + Mu + mu + 0.5 + real + + + Gain Mu + gain_mu + 0.005 + real + + + Symbol Rate + symbol_rate + samp_rate/4. + real + + + Omega Limit + omega_limit + 0.005 + real Grid Position diff --git a/grc/data/platforms/python/blocks/wxgui_fftsink2.xml b/grc/data/platforms/python/blocks/wxgui_fftsink2.xml index 6c6b747b..c729bea4 100644 --- a/grc/data/platforms/python/blocks/wxgui_fftsink2.xml +++ b/grc/data/platforms/python/blocks/wxgui_fftsink2.xml @@ -12,7 +12,6 @@ self.GetWin(), baseband_freq=$baseband_freq, y_per_div=$y_per_div, - y_divs=$y_divs, ref_level=$ref_level, sample_rate=$samp_rate, fft_size=$fft_size, @@ -68,16 +67,30 @@ self.GridAdd(self.$(id).win, $grid_pos[0], $grid_pos[1], $grid_pos[2], $grid_pos Y per Div y_per_div 10 - real - - - Y Divs - y_divs - 8 - real + enum + + + + + - Reference Level + Ref Level (dB) ref_level 50 real @@ -95,39 +108,42 @@ self.GridAdd(self.$(id).win, $grid_pos[0], $grid_pos[1], $grid_pos[2], $grid_pos int - Average Alpha - avg_alpha - 0 - real - - - Average - average + Peak Hold + peak_hold False enum + #if $peak_hold.eval == 'True' then 'none' else 'part'# - Peak Hold - peak_hold + Average + average False enum + #if $average.eval == 'True' then 'none' else 'part'# + + Average Alpha + avg_alpha + 0 + real + #if $average.eval == 'True' then 'none' else 'all'# + Grid Position grid_pos diff --git a/grc/data/platforms/python/blocks/wxgui_histosink2.xml b/grc/data/platforms/python/blocks/wxgui_histosink2.xml new file mode 100644 index 00000000..3d694aed --- /dev/null +++ b/grc/data/platforms/python/blocks/wxgui_histosink2.xml @@ -0,0 +1,56 @@ + + + + Histo Sink + wxgui_histosink2 + from gnuradio.wxgui import histosink_gl + histosink_gl.histo_sink_f( + self.GetWin(), + title=$title, + num_bins=$num_bins, + frame_size=$frame_size, +) +#set $grid_pos = $grid_pos.eval +#if not grid_pos +self.Add(self.$(id).win) +#else +self.GridAdd(self.$(id).win, $grid_pos[0], $grid_pos[1], $grid_pos[2], $grid_pos[3]) +#end if + set_num_bins($num_bins) + set_frame_size($frame_size) + + Title + title + Histogram Plot + string + + + Num Bins + num_bins + 27 + int + + + Frame Size + frame_size + 1000 + int + + + Grid Position + grid_pos + + grid_pos + + + in + float + + +Use the Grid Position (row, column, row span, column span) to position the graphical element in the window. + + diff --git a/grc/data/platforms/python/blocks/wxgui_numbersink2.xml b/grc/data/platforms/python/blocks/wxgui_numbersink2.xml index 3e224228..1b32993e 100644 --- a/grc/data/platforms/python/blocks/wxgui_numbersink2.xml +++ b/grc/data/platforms/python/blocks/wxgui_numbersink2.xml @@ -19,10 +19,10 @@ ref_level=$ref_level, sample_rate=$samp_rate, number_rate=$number_rate, - average=$options.average, + average=$average, avg_alpha=#if $avg_alpha.eval then $avg_alpha else 'None'#, label=$title, - peak_hold=$options.peak_hold, + peak_hold=$peak_hold, show_gauge=$show_gauge, ) #set $grid_pos = $grid_pos.eval @@ -108,34 +108,41 @@ self.GridAdd(self.$(id).win, $grid_pos[0], $grid_pos[1], $grid_pos[2], $grid_pos int - Average Alpha - avg_alpha - 0 - real - - - Options - options - none + Peak Hold + peak_hold + False enum + #if $peak_hold.eval == 'True' then 'none' else 'part'# + + + Average + average + False + enum + #if $average.eval == 'True' then 'none' else 'part'# + + + + Average Alpha + avg_alpha + 0 + real + #if $average.eval == 'True' then 'none' else 'all'# Show Gauge diff --git a/grc/data/platforms/python/blocks/wxgui_scopesink2.xml b/grc/data/platforms/python/blocks/wxgui_scopesink2.xml index 819a8e9a..1af0afb3 100644 --- a/grc/data/platforms/python/blocks/wxgui_scopesink2.xml +++ b/grc/data/platforms/python/blocks/wxgui_scopesink2.xml @@ -13,21 +13,18 @@ self.GetWin(), title=$title, sample_rate=$samp_rate, - frame_decim=$frame_decim, - v_scale=#if $v_scale.eval then $v_scale else 'None'#, + v_scale=$v_scale, t_scale=$t_scale, + ac_couple=$ac_couple, + xy_mode=$xy_mode, num_inputs=$num_inputs, ) -self.$(id).win.$(marker)() #set $grid_pos = $grid_pos.eval #if not grid_pos self.Add(self.$(id).win) #else self.GridAdd(self.$(id).win, $grid_pos[0], $grid_pos[1], $grid_pos[2], $grid_pos[3]) #end if - set_sample_rate($samp_rate) Type @@ -57,12 +54,6 @@ $(id).win.info.scopesink.set_trigger_mode(gr.$(trigger_mode)) --> samp_rate real - - Frame Decimation - frame_decim - 15 - int - V Scale v_scale @@ -72,51 +63,39 @@ $(id).win.info.scopesink.set_trigger_mode(gr.$(trigger_mode)) --> T Scale t_scale - .001 + 0 real - Marker - marker - set_format_line + AC Couple + ac_couple + False enum + #if $ac_couple.eval == 'True' then 'none' else 'part'# - - + Num Inputs num_inputs @@ -129,6 +108,7 @@ $(id).win.info.scopesink.set_trigger_mode(gr.$(trigger_mode)) --> grid_pos + not $xy_mode or '$type' == 'complex' or $num_inputs != 1 in $type @@ -137,6 +117,10 @@ $(id).win.info.scopesink.set_trigger_mode(gr.$(trigger_mode)) --> Set the V Scale to 0 for the scope to auto-scale. +Set the T Scale to 0 for automatic setting. + +XY Mode allows the scope to initialize as an XY plotter. + Use the Grid Position (row, column, row span, column span) to position the graphical element in the window. diff --git a/grc/examples/simple/ber_simulation.grc b/grc/examples/simple/ber_simulation.grc index 0e4ce64e..8d7d7456 100644 --- a/grc/examples/simple/ber_simulation.grc +++ b/grc/examples/simple/ber_simulation.grc @@ -1,6 +1,6 @@ - Thu Jul 24 14:28:06 2008 + Thu Mar 19 11:08:59 2009 options @@ -36,78 +36,12 @@ Custom - _coordinate - (16, 10) - - - _rotation - 0 - - - - gr_add_vxx - - id - gr_add_vxx - - - _enabled - True - - - type - complex - - - num_inputs - 2 - - - vlen - 1 - - - _coordinate - (652, 395) - - - _rotation - 0 - - - - wxgui_constellationsink2 - - id - wxgui_constellationsink2 - - - _enabled - True - - - title - "Constellation: "+str(const) - - - samp_rate - samp_rate - - - frame_decim - 15 - - - marker - set_format_plus - - - grid_pos - 2, 0, 1, 1 + realtime_scheduling + _coordinate - (907, 334) + (16, 10) _rotation @@ -239,12 +173,16 @@ 15 - avg_alpha - 0 + peak_hold + False + + + average + False - options - none + avg_alpha + 0 show_gauge @@ -306,7 +244,7 @@ type - "BER" + 'BER' win_size @@ -483,6 +421,92 @@ 0 + + gr_add_vxx + + id + gr_add_vxx + + + _enabled + True + + + type + complex + + + num_inputs + 2 + + + vlen + 1 + + + _coordinate + (652, 395) + + + _rotation + 0 + + + + wxgui_scopesink2 + + id + wxgui_scopesink2_0 + + + _enabled + True + + + type + complex + + + title + "Constellation: "+str(const) + + + samp_rate + samp_rate + + + v_scale + 0 + + + t_scale + 0 + + + ac_couple + False + + + xy_mode + True + + + num_inputs + 1 + + + grid_pos + 2, 0, 1, 1 + + + _coordinate + (828, 368) + + + _rotation + 0 + + blks2_error_rate wxgui_numbersink2 @@ -520,21 +544,21 @@ 1 - gr_add_vxx - wxgui_constellationsink2 + random_source_x + gr_throttle 0 0 random_source_x - gr_throttle + gr_chunks_to_symbols_xx 0 0 - random_source_x - gr_chunks_to_symbols_xx + gr_add_vxx + wxgui_scopesink2_0 0 0 - \ No newline at end of file + diff --git a/grc/examples/trellis/interference_cancellation.grc b/grc/examples/trellis/interference_cancellation.grc index 4065845f..32e09f7f 100644 --- a/grc/examples/trellis/interference_cancellation.grc +++ b/grc/examples/trellis/interference_cancellation.grc @@ -1,6 +1,6 @@ - Tue Nov 18 00:48:20 2008 + Thu Mar 19 11:22:40 2009 options @@ -35,6 +35,10 @@ category Custom + + realtime_scheduling + + _coordinate (10, 10) @@ -410,37 +414,6 @@ 0 - - gr_add_vxx - - id - gr_add_vxx_1 - - - _enabled - True - - - type - complex - - - num_inputs - 2 - - - vlen - 1 - - - _coordinate - (1400, 262) - - - _rotation - 0 - - gr_noise_source_x @@ -476,45 +449,6 @@ 0 - - wxgui_constellationsink2 - - id - wxgui_constellationsink2_0 - - - _enabled - True - - - title - Constellation Plot - - - samp_rate - R - - - frame_decim - 15 - - - marker - set_format_plus - - - grid_pos - - - - _coordinate - (1301, 74) - - - _rotation - 0 - - gr_sub_xx @@ -529,6 +463,10 @@ type short + + vlen + 1 + num_inputs 2 @@ -606,6 +544,10 @@ type short + + vlen + 1 + num_inputs 2 @@ -749,6 +691,10 @@ type complex + + vlen + 1 + num_inputs 2 @@ -776,6 +722,10 @@ type short + + vlen + 1 + num_inputs 2 @@ -919,6 +869,10 @@ type complex + + vlen + 1 + num_inputs 2 @@ -946,6 +900,10 @@ type short + + vlen + 1 + num_inputs 2 @@ -1064,12 +1022,16 @@ 15 - avg_alpha - 0.001 + peak_hold + False - options - average + average + False + + + avg_alpha + 0.001 show_gauge @@ -1143,12 +1105,16 @@ 15 - avg_alpha - 0.001 + peak_hold + False - options - average + average + False + + + avg_alpha + 0.001 show_gauge @@ -1222,103 +1188,28 @@ 15 - avg_alpha - 0.001 - - - options - average - - - show_gauge - True - - - grid_pos - 1,0,1,1 - - - _coordinate - (1269, 1417) - - - _rotation - 0 + peak_hold + False - - - wxgui_numbersink2 - id - wxgui_numbersink2_0 - - - _enabled - True - - - type - float - - - title - BER 1 (raw) - - - units - BER - - - samp_rate - R - - - base_value - 0.0 - - - min_value - 0 - - - max_value - 1 - - - factor - 1.0 - - - decimal_places - 6 - - - ref_level - 0 - - - number_rate - 15 + average + False avg_alpha 0.001 - - options - average - show_gauge True grid_pos - 0,0,1,1 + 1,0,1,1 _coordinate - (1267, 410) + (1269, 1417) _rotation @@ -1715,6 +1606,175 @@ 0 + + gr_add_vxx + + id + gr_add_vxx_1 + + + _enabled + True + + + type + complex + + + num_inputs + 2 + + + vlen + 1 + + + _coordinate + (1400, 262) + + + _rotation + 0 + + + + wxgui_numbersink2 + + id + wxgui_numbersink2_0 + + + _enabled + True + + + type + float + + + title + BER 1 (raw) + + + units + BER + + + samp_rate + R + + + base_value + 0.0 + + + min_value + 0 + + + max_value + 1 + + + factor + 1.0 + + + decimal_places + 6 + + + ref_level + 0 + + + number_rate + 15 + + + peak_hold + False + + + average + False + + + avg_alpha + 0.001 + + + show_gauge + True + + + grid_pos + 0,0,1,1 + + + _coordinate + (1267, 410) + + + _rotation + 0 + + + + wxgui_scopesink2 + + id + wxgui_scopesink2_0 + + + _enabled + True + + + type + complex + + + title + Scope Plot + + + samp_rate + R + + + v_scale + 0 + + + t_scale + 0 + + + ac_couple + False + + + xy_mode + True + + + num_inputs + 1 + + + grid_pos + + + + _coordinate + (1533, 149) + + + _rotation + 0 + + random_source_x_1 trellis_encoder_xx_1 @@ -1757,12 +1817,6 @@ 0 1 - - gr_add_vxx_1 - wxgui_constellationsink2_0 - 0 - 0 - gr_chunks_to_symbols_xx_1 gr_multiply_const_vxx_1 @@ -2009,4 +2063,10 @@ 0 1 + + gr_add_vxx_1 + wxgui_scopesink2_0 + 0 + 0 + diff --git a/grc/src/grc_gnuradio/wxgui/callback_controls.py b/grc/src/grc_gnuradio/wxgui/callback_controls.py index 2f474cf9..32e5d884 100644 --- a/grc/src/grc_gnuradio/wxgui/callback_controls.py +++ b/grc/src/grc_gnuradio/wxgui/callback_controls.py @@ -26,7 +26,7 @@ class LabelText(wx.StaticText): """Label text class for uniform labels among all controls.""" def __init__(self, window, label): - wx.StaticText.__init__(self, window, -1, str(label)) + wx.StaticText.__init__(self, window, label=str(label)) font = self.GetFont() font.SetWeight(wx.FONTWEIGHT_BOLD) self.SetFont(font) @@ -92,7 +92,7 @@ class button_control(_chooser_control_base): """House a button for variable control.""" def _init(self): - self.button = wx.Button(self.get_window(), -1, self.labels[self.index]) + self.button = wx.Button(self.get_window(), label=self.labels[self.index]) self.button.Bind(wx.EVT_BUTTON, self._handle_changed) self.Add(self.button, 0, wx.ALIGN_CENTER) @@ -107,7 +107,7 @@ class drop_down_control(_chooser_control_base): """House a drop down for variable control.""" def _init(self): - self.drop_down = wx.Choice(self.get_window(), -1, choices=self.labels) + self.drop_down = wx.Choice(self.get_window(), choices=self.labels) self.Add(self.drop_down, 0, wx.ALIGN_CENTER) self.drop_down.Bind(wx.EVT_CHOICE, self._handle_changed) self.drop_down.SetSelection(self.index) @@ -124,13 +124,13 @@ class _radio_buttons_control_base(_chooser_control_base): def _init(self): #create box for radio buttons radio_box = wx.BoxSizer(self.radio_box_orientation) - panel = wx.Panel(self.get_window(), -1) + panel = wx.Panel(self.get_window()) panel.SetSizer(radio_box) self.Add(panel, 0, wx.ALIGN_CENTER) #create radio buttons self.radio_buttons = list() for label in self.labels: - radio_button = wx.RadioButton(panel, -1, label) + radio_button = wx.RadioButton(panel, label=label) radio_button.SetValue(False) self.radio_buttons.append(radio_button) radio_box.Add(radio_button, 0, self.radio_button_align) @@ -177,13 +177,13 @@ class _slider_control_base(_control_base): #create gui elements label_text_sizer = wx.BoxSizer(self.label_text_orientation) #label and text box container label_text = LabelText(self.get_window(), '%s: '%str(label)) - self.text_box = text_box = wx.TextCtrl(self.get_window(), -1, str(value), style=wx.TE_PROCESS_ENTER) + self.text_box = text_box = wx.TextCtrl(self.get_window(), style=wx.TE_PROCESS_ENTER) text_box.Bind(wx.EVT_TEXT_ENTER, self._handle_enter) #bind this special enter hotkey event for obj in (label_text, text_box): #fill the container with label and text entry box - label_text_sizer.Add(obj, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL) + label_text_sizer.Add(obj, 0, wx.ALIGN_CENTER) self.Add(label_text_sizer, 0, wx.ALIGN_CENTER) #make the slider - self.slider = slider = wx.Slider(self.get_window(), -1, size=wx.Size(*self.get_slider_size()), style=self.slider_style) + self.slider = slider = wx.Slider(self.get_window(), size=wx.Size(*self.get_slider_size()), style=self.slider_style) try: slider.SetRange(0, num_steps) except Exception, e: print >> sys.stderr, 'Error in set slider range: "%s".'%e @@ -245,11 +245,11 @@ class _slider_control_base(_control_base): class slider_horizontal_control(_slider_control_base): label_text_orientation = wx.HORIZONTAL slider_style = wx.SL_HORIZONTAL - def get_slider_size(self): return self.slider_length, 20 + def get_slider_size(self): return self.slider_length, -1 class slider_vertical_control(_slider_control_base): label_text_orientation = wx.VERTICAL slider_style = wx.SL_VERTICAL - def get_slider_size(self): return 20, self.slider_length + def get_slider_size(self): return -1, self.slider_length ############################################################################################## # Text Box Control @@ -271,10 +271,10 @@ class text_box_control(_control_base): #create gui elements label_text_sizer = wx.BoxSizer(wx.HORIZONTAL) #label and text box container label_text = LabelText(self.get_window(), '%s: '%str(label)) - self.text_box = text_box = wx.TextCtrl(self.get_window(), -1, str(value), style=wx.TE_PROCESS_ENTER) + self.text_box = text_box = wx.TextCtrl(self.get_window(), value=str(value), style=wx.TE_PROCESS_ENTER) text_box.Bind(wx.EVT_TEXT_ENTER, self._handle_enter) #bind this special enter hotkey event for obj in (label_text, text_box): #fill the container with label and text entry box - label_text_sizer.Add(obj, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL) + label_text_sizer.Add(obj, 0, wx.ALIGN_CENTER) self.Add(label_text_sizer, 0, wx.ALIGN_CENTER) #detect string mode self._string_mode = isinstance(value, str) -- 2.30.2