summaryrefslogtreecommitdiff
path: root/gr-radio-astronomy/src/python/usrp_psr_receiver.py
diff options
context:
space:
mode:
authorjcorgan2006-08-03 04:51:51 +0000
committerjcorgan2006-08-03 04:51:51 +0000
commit5d69a524f81f234b3fbc41d49ba18d6f6886baba (patch)
treeb71312bf7f1e8d10fef0f3ac6f28784065e73e72 /gr-radio-astronomy/src/python/usrp_psr_receiver.py
downloadgnuradio-5d69a524f81f234b3fbc41d49ba18d6f6886baba.tar.gz
gnuradio-5d69a524f81f234b3fbc41d49ba18d6f6886baba.tar.bz2
gnuradio-5d69a524f81f234b3fbc41d49ba18d6f6886baba.zip
Houston, we have a trunk.
git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@3122 221aa14e-8319-0410-a670-987f0aec2ac5
Diffstat (limited to 'gr-radio-astronomy/src/python/usrp_psr_receiver.py')
-rwxr-xr-xgr-radio-astronomy/src/python/usrp_psr_receiver.py1101
1 files changed, 1101 insertions, 0 deletions
diff --git a/gr-radio-astronomy/src/python/usrp_psr_receiver.py b/gr-radio-astronomy/src/python/usrp_psr_receiver.py
new file mode 100755
index 000000000..8a5593706
--- /dev/null
+++ b/gr-radio-astronomy/src/python/usrp_psr_receiver.py
@@ -0,0 +1,1101 @@
+#!/usr/bin/env python
+#
+# Copyright 2004,2005 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 2, 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., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+#
+
+
+#
+#
+# Pulsar receiver application
+#
+# Performs both harmonic folding analysis
+# and epoch folding analysis
+#
+#
+from gnuradio import gr, gru, blks, audio
+from gnuradio import usrp, optfir
+from gnuradio import eng_notation
+from gnuradio.eng_option import eng_option
+from gnuradio.wxgui import stdgui, ra_fftsink, ra_stripchartsink, form, slider
+from optparse import OptionParser
+import wx
+import sys
+import Numeric
+import FFT
+import ephem
+import time
+import os
+import math
+
+
+class app_flow_graph(stdgui.gui_flow_graph):
+ def __init__(self, frame, panel, vbox, argv):
+ stdgui.gui_flow_graph.__init__(self)
+
+ self.frame = frame
+ self.panel = panel
+
+ parser = OptionParser(option_class=eng_option)
+ parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=(0, 0),
+ help="select USRP Rx side A or B (default=A)")
+ parser.add_option("-d", "--decim", type="int", default=16,
+ help="set fgpa decimation rate to DECIM [default=%default]")
+ parser.add_option("-f", "--freq", type="eng_float", default=None,
+ help="set frequency to FREQ", metavar="FREQ")
+ parser.add_option("-Q", "--observing", type="eng_float", default=0.0,
+ help="set observing frequency to FREQ")
+ parser.add_option("-a", "--avg", type="eng_float", default=1.0,
+ help="set spectral averaging alpha")
+ parser.add_option("-V", "--favg", type="eng_float", default=2.0,
+ help="set folder averaging alpha")
+ parser.add_option("-g", "--gain", type="eng_float", default=None,
+ help="set gain in dB (default is midpoint)")
+ parser.add_option("-l", "--reflevel", type="eng_float", default=30.0,
+ help="Set pulse display reference level")
+ parser.add_option("-L", "--lowest", type="eng_float", default=1.5,
+ help="Lowest valid frequency bin")
+ parser.add_option("-e", "--longitude", type="eng_float", default=-76.02, help="Set Observer Longitude")
+ parser.add_option("-c", "--latitude", type="eng_float", default=44.85, help="Set Observer Latitude")
+ parser.add_option("-F", "--fft_size", type="eng_float", default=1024, help="Size of FFT")
+
+ parser.add_option ("-t", "--threshold", type="eng_float", default=2.5, help="pulsar threshold")
+ parser.add_option("-p", "--lowpass", type="eng_float", default=100, help="Pulse spectra cutoff freq")
+ parser.add_option("-P", "--prefix", default="./", help="File prefix")
+ parser.add_option("-u", "--pulsefreq", type="eng_float", default=0.748, help="Observation pulse rate")
+ parser.add_option("-D", "--dm", type="eng_float", default=1.0e-5, help="Dispersion Measure")
+ parser.add_option("-O", "--doppler", type="eng_float", default=1.0, help="Doppler ratio")
+ parser.add_option("-B", "--divbase", type="eng_float", default=20, help="Y/Div menu base")
+ parser.add_option("-I", "--division", type="eng_float", default=100, help="Y/Div")
+ (options, args) = parser.parse_args()
+ if len(args) != 0:
+ parser.print_help()
+ sys.exit(1)
+
+ self.show_debug_info = True
+
+ self.reflevel = options.reflevel
+ self.divbase = options.divbase
+ self.division = options.division
+
+ # Low-pass cutoff for post-detector filter
+ # Set to 100Hz usually, since lots of pulsars fit in this
+ # range
+ self.lowpass = options.lowpass
+
+ # What is lowest valid frequency bin in post-detector FFT?
+ # There's some pollution very close to DC
+ self.lowest_freq = options.lowest
+
+ # What (dB) threshold to use in determining spectral candidates
+ self.threshold = options.threshold
+
+ # Filename prefix for recording file
+ self.prefix = options.prefix
+
+ # Dispersion Measure (DM)
+ self.dm = options.dm
+
+ # Doppler shift, as a ratio
+ # 1.0 == no doppler shift
+ # 1.005 == a little negative shift
+ # 0.995 == a little positive shift
+ self.doppler = options.doppler
+
+ #
+ # Input frequency and observing frequency--not necessarily the
+ # same thing, if we're looking at the IF of some downconverter
+ # that's ahead of the USRP and daughtercard. This distinction
+ # is important in computing the correct de-dispersion filter.
+ #
+ self.frequency = options.freq
+ if options.observing <= 0:
+ self.observing_freq = options.freq
+ else:
+ self.observing_freq = options.observing
+
+ # build the graph
+ self.u = usrp.source_c(decim_rate=options.decim)
+ self.u.set_mux(usrp.determine_rx_mux_value(self.u, options.rx_subdev_spec))
+
+ #
+ # Recording file, in case we ever need to record baseband data
+ #
+ self.recording = gr.file_sink(gr.sizeof_char, "/dev/null")
+ self.recording_state = False
+
+ self.pulse_recording = gr.file_sink(gr.sizeof_short, "/dev/null")
+ self.pulse_recording_state = False
+
+ #
+ # We come up with recording turned off, but the user may
+ # request recording later on
+ self.recording.close()
+ self.pulse_recording.close()
+
+ #
+ # Need these two for converting 12-bit baseband signals to 8-bit
+ #
+ self.tofloat = gr.complex_to_float()
+ self.tochar = gr.float_to_char()
+
+ # Need this for recording pulses (post-detector)
+ self.toshort = gr.float_to_short()
+
+
+ #
+ # The spectral measurer sets this when it has a valid
+ # average spectral peak-to-peak distance
+ # We can then use this to program the parameters for the epoch folder
+ #
+ # We set a sentimental value here
+ self.pulse_freq = options.pulsefreq
+
+ # Folder runs at this raw sample rate
+ self.folder_input_rate = 20000
+
+ # Each pulse in the epoch folder is sampled at 128 times the nominal
+ # pulse rate
+ self.folding = 128
+
+
+ #
+ # Try to find candidate parameters for rational resampler
+ #
+ save_i = 0
+ candidates = []
+ for i in range(20,300):
+ input_rate = self.folder_input_rate
+ output_rate = int(self.pulse_freq * i)
+ interp = gru.lcm(input_rate, output_rate) / input_rate
+ decim = gru.lcm(input_rate, output_rate) / output_rate
+ if (interp < 500 and decim < 250000):
+ candidates.append(i)
+
+ # We didn't find anything, bail!
+ if (len(candidates) < 1):
+ print "Couldn't converge on resampler parameters"
+ sys.exit(1)
+
+ #
+ # Now try to find candidate with the least sampling error
+ #
+ mindiff = 999.999
+ for i in candidates:
+ diff = self.pulse_freq * i
+ diff = diff - int(diff)
+ if (diff < mindiff):
+ mindiff = diff
+ save_i = i
+
+ # Recompute rates
+ input_rate = self.folder_input_rate
+ output_rate = int(self.pulse_freq * save_i)
+
+ # Compute new interp and decim, based on best candidate
+ interp = gru.lcm(input_rate, output_rate) / input_rate
+ decim = gru.lcm(input_rate, output_rate) / output_rate
+
+ # Save optimized folding parameters, used later
+ self.folding = save_i
+ self.interp = int(interp)
+ self.decim = int(decim)
+
+ # So that we can view 4 pulses in the pulse viewer window
+ FOLD_MULT=4
+
+ # determine the daughterboard subdevice we're using
+ self.subdev = usrp.selected_subdev(self.u, options.rx_subdev_spec)
+
+ # Compute raw input rate
+ input_rate = self.u.adc_freq() / self.u.decim_rate()
+
+ # BW==input_rate for complex data
+ self.bw = input_rate
+
+ #
+ # We use this as a crude volume control for the audio output
+ #
+ self.volume = gr.multiply_const_ff(10**(-1))
+
+
+ #
+ # Create location data for ephem package
+ #
+ self.locality = ephem.Observer()
+ self.locality.long = str(options.longitude)
+ self.locality.lat = str(options.latitude)
+
+ #
+ # What is the post-detector LPF cutoff for the FFT?
+ #
+ PULSAR_MAX_FREQ=int(options.lowpass)
+
+ # First low-pass filters down to input_rate/FIRST_FACTOR
+ # and decimates appropriately
+ FIRST_FACTOR=int(input_rate/(self.folder_input_rate/2))
+ first_filter = gr.firdes.low_pass (1.0,
+ input_rate,
+ input_rate/FIRST_FACTOR,
+ input_rate/(FIRST_FACTOR*20),
+ gr.firdes.WIN_HAMMING)
+
+ # Second filter runs at the output rate of the first filter,
+ # And low-pass filters down to PULSAR_MAX_FREQ*10
+ #
+ second_input_rate = int(input_rate/(FIRST_FACTOR/2))
+ second_filter = gr.firdes.band_pass(1.0, second_input_rate,
+ 0.10,
+ PULSAR_MAX_FREQ*10,
+ PULSAR_MAX_FREQ*1.5,
+ gr.firdes.WIN_HAMMING)
+
+ # Third filter runs at PULSAR_MAX_FREQ*20
+ # and filters down to PULSAR_MAX_FREQ
+ #
+ third_input_rate = PULSAR_MAX_FREQ*20
+ third_filter = gr.firdes_band_pass(1.0, third_input_rate,
+ 0.10, PULSAR_MAX_FREQ,
+ PULSAR_MAX_FREQ/10.0,
+ gr.firdes.WIN_HAMMING)
+
+
+ #
+ # Create the appropriate FFT scope
+ #
+ self.scope = ra_fftsink.ra_fft_sink_f (self, panel,
+ fft_size=int(options.fft_size), sample_rate=PULSAR_MAX_FREQ*2,
+ title="Post-detector spectrum",
+ cfunc=self.pulsarfunc, xydfunc=self.xydfunc, fft_rate=200)
+
+ #
+ # Tell scope we're looking from DC to PULSAR_MAX_FREQ
+ #
+ self.scope.set_baseband_freq (0.0)
+
+
+ #
+ # Setup stripchart for showing pulse profiles
+ #
+ hz = "%5.3fHz " % self.pulse_freq
+ per = "(%5.3f sec)" % (1.0/self.pulse_freq)
+ sr = "%d sps" % (int(self.pulse_freq*self.folding))
+ self.chart = ra_stripchartsink.stripchart_sink_f (self, panel,
+ sample_rate=1,
+ stripsize=self.folding*FOLD_MULT, parallel=True, title="Pulse Profiles: "+hz+per,
+ xlabel="Seconds @ "+sr, ylabel="Level", autoscale=True,
+ divbase=self.divbase, scaling=1.0/(self.folding*self.pulse_freq))
+ self.chart.set_ref_level(self.reflevel)
+ self.chart.set_y_per_div(self.division)
+
+ # De-dispersion filter setup
+ #
+ # Do this here, just before creating the filter
+ # that will use the taps.
+ #
+ ntaps = self.compute_disp_ntaps(self.dm,self.bw,self.observing_freq)
+
+ # Taps for the de-dispersion filter
+ self.disp_taps = Numeric.zeros(ntaps,Numeric.Complex64)
+
+ # Compute the de-dispersion filter now
+ self.compute_dispfilter(self.dm,self.doppler,
+ self.bw,self.observing_freq)
+
+ #
+ # Call constructors for receive chains
+ #
+
+ #
+ # Now create the FFT filter using the computed taps
+ self.dispfilt = gr.fft_filter_ccc(1, self.disp_taps)
+
+ #
+ # Audio sink
+ #
+ self.audio = audio.sink(second_input_rate, "plughw:0,0")
+
+ #
+ # The three post-detector filters
+ # Done this way to allow an audio path (up to 10Khz)
+ # ...and also because going from xMhz down to ~100Hz
+ # In a single filter doesn't seem to work.
+ #
+ self.first = gr.fir_filter_fff (FIRST_FACTOR/2, first_filter)
+
+ p = second_input_rate / (PULSAR_MAX_FREQ*20)
+ self.second = gr.fir_filter_fff (int(p), second_filter)
+ self.third = gr.fir_filter_fff (10, third_filter)
+
+ # Split complex USRP stream into a pair of floats
+ self.splitter = gr.complex_to_float (1);
+
+ # I squarer (detector)
+ self.multI = gr.multiply_ff();
+
+ # Q squarer (detector)
+ self.multQ = gr.multiply_ff();
+
+ # Adding squared I and Q to produce instantaneous signal power
+ self.adder = gr.add_ff();
+
+ self.enable_comb_filter = False
+ # Epoch folder comb filter
+ if self.enable_comb_filter == True:
+ bogtaps = Numeric.zeros(512, Numeric.Float64)
+ self.folder_comb = gr.fft_filter_ccc(1,bogtaps)
+
+ # Rational resampler
+ self.folder_rr = blks.rational_resampler_fff(self, self.interp, self.decim)
+
+ # Epoch folder bandpass
+ bogtaps = Numeric.zeros(1, Numeric.Float64)
+ self.folder_bandpass = gr.fir_filter_fff (1, bogtaps)
+
+ # Epoch folder F2C/C2F
+ self.folder_f2c = gr.float_to_complex()
+ self.folder_c2f = gr.complex_to_float()
+
+ # Epoch folder S2P
+ self.folder_s2p = gr.serial_to_parallel (gr.sizeof_float,
+ self.folding*FOLD_MULT)
+
+ # Epoch folder IIR Filter (produces average pulse profiles)
+ self.folder_iir = gr.single_pole_iir_filter_ff(1.0/options.favg,
+ self.folding*FOLD_MULT)
+
+ #
+ # Set all the epoch-folder goop up
+ #
+ self.set_folding_params()
+
+ #
+ # Start connecting configured modules in the receive chain
+ #
+
+ # Connect raw USRP to de-dispersion filter, complex->float splitter
+ self.connect(self.u, self.dispfilt, self.splitter)
+
+ # Connect splitter outputs to multipliers
+ # First do I^2
+ self.connect((self.splitter, 0), (self.multI,0))
+ self.connect((self.splitter, 0), (self.multI,1))
+
+ # Then do Q^2
+ self.connect((self.splitter, 1), (self.multQ,0))
+ self.connect((self.splitter, 1), (self.multQ,1))
+
+ # Then sum the squares
+ self.connect(self.multI, (self.adder,0))
+ self.connect(self.multQ, (self.adder,1))
+
+ # Connect detector/adder output to FIR LPF
+ # in two stages, followed by the FFT scope
+ self.connect(self.adder, self.first,
+ self.second, self.third, self.scope)
+
+ # Connect audio output
+ self.connect(self.first, self.volume)
+ self.connect(self.volume, (self.audio, 0))
+ self.connect(self.volume, (self.audio, 1))
+
+ # Connect epoch folder
+ if self.enable_comb_filter == True:
+ self.connect (self.first, self.folder_bandpass, self.folder_rr,
+ self.folder_f2c,
+ self.folder_comb, self.folder_c2f,
+ self.folder_s2p, self.folder_iir,
+ self.chart)
+
+ else:
+ self.connect (self.first, self.folder_bandpass, self.folder_rr,
+ self.folder_s2p, self.folder_iir, self.chart)
+
+ # Connect baseband recording file (initially /dev/null)
+ self.connect(self.u, self.tofloat, self.tochar, self.recording)
+
+ # Connect pulse recording file (initially /dev/null)
+ self.connect(self.first, self.toshort, self.pulse_recording)
+
+ #
+ # Build the GUI elements
+ #
+ self._build_gui(vbox)
+
+ # Make GUI agree with command-line
+ self.myform['average'].set_value(int(options.avg))
+ self.myform['foldavg'].set_value(int(options.favg))
+
+
+ # Make spectral averager agree with command line
+ if options.avg != 1.0:
+ self.scope.set_avg_alpha(float(1.0/options.avg))
+ self.scope.set_average(True)
+
+
+ # set initial values
+
+ if options.gain is None:
+ # if no gain was specified, use the mid-point in dB
+ g = self.subdev.gain_range()
+ options.gain = float(g[0]+g[1])/2
+
+ if options.freq is None:
+ # if no freq was specified, use the mid-point
+ r = self.subdev.freq_range()
+ options.freq = float(r[0]+r[1])/2
+
+ self.set_gain(options.gain)
+ self.set_volume(-10.0)
+
+ if not(self.set_freq(options.freq)):
+ self._set_status_msg("Failed to set initial frequency")
+
+ self.myform['decim'].set_value(self.u.decim_rate())
+ self.myform['fs@usb'].set_value(self.u.adc_freq() / self.u.decim_rate())
+ self.myform['dbname'].set_value(self.subdev.name())
+ self.myform['DM'].set_value(self.dm)
+ self.myform['Doppler'].set_value(self.doppler)
+
+ #
+ # Start the timer that shows current LMST on the GUI
+ #
+ self.lmst_timer.Start(1000)
+
+
+ def _set_status_msg(self, msg):
+ self.frame.GetStatusBar().SetStatusText(msg, 0)
+
+ def _build_gui(self, vbox):
+
+ def _form_set_freq(kv):
+ return self.set_freq(kv['freq'])
+
+ def _form_set_dm(kv):
+ return self.set_dm(kv['DM'])
+
+ def _form_set_doppler(kv):
+ return self.set_doppler(kv['Doppler'])
+
+ # Position the FFT or Waterfall
+ vbox.Add(self.scope.win, 5, wx.EXPAND)
+ vbox.Add(self.chart.win, 5, wx.EXPAND)
+
+ # add control area at the bottom
+ self.myform = myform = form.form()
+ hbox = wx.BoxSizer(wx.HORIZONTAL)
+ hbox.Add((7,0), 0, wx.EXPAND)
+ vbox1 = wx.BoxSizer(wx.VERTICAL)
+ myform['freq'] = form.float_field(
+ parent=self.panel, sizer=vbox1, label="Center freq", weight=1,
+ callback=myform.check_input_and_call(_form_set_freq, self._set_status_msg))
+
+ vbox1.Add((3,0), 0, 0)
+
+ # To show current Local Mean Sidereal Time
+ myform['lmst_high'] = form.static_text_field(
+ parent=self.panel, sizer=vbox1, label="Current LMST", weight=1)
+ vbox1.Add((3,0), 0, 0)
+
+ # To show current spectral cursor data
+ myform['spec_data'] = form.static_text_field(
+ parent=self.panel, sizer=vbox1, label="Pulse Freq", weight=1)
+ vbox1.Add((3,0), 0, 0)
+
+ # To show best pulses found in FFT output
+ myform['best_pulse'] = form.static_text_field(
+ parent=self.panel, sizer=vbox1, label="Best freq", weight=1)
+ vbox1.Add((3,0), 0, 0)
+
+ vboxBogus = wx.BoxSizer(wx.VERTICAL)
+ vboxBogus.Add ((2,0), 0, wx.EXPAND)
+ vbox2 = wx.BoxSizer(wx.VERTICAL)
+ g = self.subdev.gain_range()
+ myform['gain'] = form.slider_field(parent=self.panel, sizer=vbox2, label="RF Gain",
+ weight=1,
+ min=int(g[0]), max=int(g[1]),
+ callback=self.set_gain)
+
+ vbox2.Add((6,0), 0, 0)
+ myform['average'] = form.slider_field(parent=self.panel, sizer=vbox2,
+ label="Spectral Averaging", weight=1, min=1, max=200, callback=self.set_averaging)
+ vbox2.Add((6,0), 0, 0)
+ myform['foldavg'] = form.slider_field(parent=self.panel, sizer=vbox2,
+ label="Folder Averaging", weight=1, min=1, max=20, callback=self.set_folder_averaging)
+ vbox2.Add((6,0), 0, 0)
+ myform['volume'] = form.quantized_slider_field(parent=self.panel, sizer=vbox2,
+ label="Audio Volume", weight=1, range=(-20, 0, 0.5), callback=self.set_volume)
+ vbox2.Add((6,0), 0, 0)
+ myform['DM'] = form.float_field(
+ parent=self.panel, sizer=vbox2, label="DM", weight=1,
+ callback=myform.check_input_and_call(_form_set_dm))
+ vbox2.Add((6,0), 0, 0)
+ myform['Doppler'] = form.float_field(
+ parent=self.panel, sizer=vbox2, label="Doppler", weight=1,
+ callback=myform.check_input_and_call(_form_set_doppler))
+ vbox2.Add((6,0), 0, 0)
+
+
+ # Baseband recording control
+ buttonbox = wx.BoxSizer(wx.HORIZONTAL)
+ self.record_control = form.button_with_callback(self.panel,
+ label="Recording baseband: Off ",
+ callback=self.toggle_recording)
+ self.record_pulse_control = form.button_with_callback(self.panel,
+ label="Recording pulses: Off ",
+ callback=self.toggle_pulse_recording)
+
+ buttonbox.Add(self.record_control, 0, wx.CENTER)
+ buttonbox.Add(self.record_pulse_control, 0, wx.CENTER)
+ vbox.Add(buttonbox, 0, wx.CENTER)
+ hbox.Add(vbox1, 0, 0)
+ hbox.Add(vboxBogus, 0, 0)
+ hbox.Add(vbox2, wx.ALIGN_RIGHT, 0)
+ vbox.Add(hbox, 0, wx.EXPAND)
+
+ self._build_subpanel(vbox)
+
+ self.lmst_timer = wx.PyTimer(self.lmst_timeout)
+ self.lmst_timeout()
+
+
+ def _build_subpanel(self, vbox_arg):
+ # build a secondary information panel (sometimes hidden)
+
+ # FIXME figure out how to have this be a subpanel that is always
+ # created, but has its visibility controlled by foo.Show(True/False)
+
+ if not(self.show_debug_info):
+ return
+
+ panel = self.panel
+ vbox = vbox_arg
+ myform = self.myform
+
+ #panel = wx.Panel(self.panel, -1)
+ #vbox = wx.BoxSizer(wx.VERTICAL)
+
+ hbox = wx.BoxSizer(wx.HORIZONTAL)
+ hbox.Add((5,0), 0)
+ myform['decim'] = form.static_float_field(
+ parent=panel, sizer=hbox, label="Decim")
+
+ hbox.Add((5,0), 1)
+ myform['fs@usb'] = form.static_float_field(
+ parent=panel, sizer=hbox, label="Fs@USB")
+
+ hbox.Add((5,0), 1)
+ myform['dbname'] = form.static_text_field(
+ parent=panel, sizer=hbox)
+
+ hbox.Add((5,0), 1)
+ myform['baseband'] = form.static_float_field(
+ parent=panel, sizer=hbox, label="Analog BB")
+
+ hbox.Add((5,0), 1)
+ myform['ddc'] = form.static_float_field(
+ parent=panel, sizer=hbox, label="DDC")
+
+ hbox.Add((5,0), 0)
+ vbox.Add(hbox, 0, wx.EXPAND)
+
+
+
+ def set_freq(self, target_freq):
+ """
+ Set the center frequency we're interested in.
+
+ @param target_freq: frequency in Hz
+ @rypte: bool
+
+ Tuning is a two step process. First we ask the front-end to
+ tune as close to the desired frequency as it can. Then we use
+ the result of that operation and our target_frequency to
+ determine the value for the digital down converter.
+ """
+ r = usrp.tune(self.u, 0, self.subdev, target_freq)
+
+ if r:
+ self.myform['freq'].set_value(target_freq) # update displayed value
+ self.myform['baseband'].set_value(r.baseband_freq)
+ self.myform['ddc'].set_value(r.dxc_freq)
+ # Adjust self.frequency, and self.observing_freq
+ # We pick up the difference between the current self.frequency
+ # and the just-programmed one, and use this to adjust
+ # self.observing_freq. We have to do it this way to
+ # make the dedispersion filtering work out properly.
+ delta = target_freq - self.frequency
+ self.frequency = target_freq
+ self.observing_freq += delta
+
+ # Now that we're adjusted, compute a new dispfilter, and
+ # set the taps for the FFT filter.
+ ntaps = self.compute_disp_ntaps(self.dm, self.bw, self.observing_freq)
+ self.disp_taps = Numeric.zeros(ntaps, Numeric.Complex64)
+ self.compute_dispfilter(self.dm,self.doppler,self.bw,
+ self.observing_freq)
+ self.dispfilt.set_taps(self.disp_taps)
+
+ return True
+
+ return False
+
+ # Callback for gain-setting slider
+ def set_gain(self, gain):
+ self.myform['gain'].set_value(gain) # update displayed value
+ self.subdev.set_gain(gain)
+
+
+ def set_volume(self, vol):
+ self.myform['volume'].set_value(vol)
+ self.volume.set_k((10**(vol/10))/8192)
+
+ # Callback for spectral-averaging slider
+ def set_averaging(self, avval):
+ self.myform['average'].set_value(avval)
+ self.scope.set_avg_alpha(1.0/(avval))
+ self.scope.set_average(True)
+
+ def set_folder_averaging(self, avval):
+ self.myform['foldavg'].set_value(avval)
+ self.folder_iir.set_taps(1.0/avval)
+
+ # Timer callback to update LMST display
+ def lmst_timeout(self):
+ self.locality.date = ephem.now()
+ sidtime = self.locality.sidereal_time()
+ self.myform['lmst_high'].set_value(str(ephem.hours(sidtime)))
+
+ #
+ # Turn recording on/off
+ # Called-back by "Recording" button
+ #
+ def toggle_recording(self):
+ # Pick up current LMST
+ self.locality.date = ephem.now()
+ sidtime = self.locality.sidereal_time()
+
+ # Pick up localtime, for generating filenames
+ foo = time.localtime()
+
+ # Generate filenames for both data and header file
+ filename = "%04d%02d%02d%02d%02d.pdat" % (foo.tm_year, foo.tm_mon,
+ foo.tm_mday, foo.tm_hour, foo.tm_min)
+ hdrfilename = "%04d%02d%02d%02d%02d.phdr" % (foo.tm_year, foo.tm_mon,
+ foo.tm_mday, foo.tm_hour, foo.tm_min)
+
+ # Current recording? Flip state
+ if (self.recording_state == True):
+ self.recording_state = False
+ self.record_control.SetLabel("Recording baseband: Off ")
+ self.recording.close()
+ # Not recording?
+ else:
+ self.recording_state = True
+ self.record_control.SetLabel("Recording baseband to: "+filename)
+
+ # Cause gr_file_sink object to accept new filename
+ # note use of self.prefix--filename prefix from
+ # command line (defaults to ./)
+ #
+ self.recording.open (self.prefix+filename)
+
+ #
+ # We open the header file as a regular file, write header data,
+ # then close
+ hdrf = open(self.prefix+hdrfilename, "w")
+ hdrf.write("receiver center frequency: "+str(self.frequency)+"\n")
+ hdrf.write("observing frequency: "+str(self.observing_freq)+"\n")
+ hdrf.write("DM: "+str(self.dm)+"\n")
+ hdrf.write("doppler: "+str(self.doppler)+"\n")
+
+ hdrf.write("sidereal: "+str(ephem.hours(sidtime))+"\n")
+ hdrf.write("bandwidth: "+str(self.u.adc_freq() / self.u.decim_rate())+"\n")
+ hdrf.write("sample type: complex_char\n")
+ hdrf.write("sample size: "+str(gr.sizeof_char*2)+"\n")
+ hdrf.close()
+ #
+ # Turn recording on/off
+ # Called-back by "Recording" button
+ #
+ def toggle_pulse_recording(self):
+ # Pick up current LMST
+ self.locality.date = ephem.now()
+ sidtime = self.locality.sidereal_time()
+
+ # Pick up localtime, for generating filenames
+ foo = time.localtime()
+
+ # Generate filenames for both data and header file
+ filename = "%04d%02d%02d%02d%02d.padat" % (foo.tm_year, foo.tm_mon,
+ foo.tm_mday, foo.tm_hour, foo.tm_min)
+ hdrfilename = "%04d%02d%02d%02d%02d.pahdr" % (foo.tm_year, foo.tm_mon,
+ foo.tm_mday, foo.tm_hour, foo.tm_min)
+
+ # Current recording? Flip state
+ if (self.pulse_recording_state == True):
+ self.pulse_recording_state = False
+ self.record_pulse_control.SetLabel("Recording pulses: Off ")
+ self.pulse_recording.close()
+ # Not recording?
+ else:
+ self.pulse_recording_state = True
+ self.record_pulse_control.SetLabel("Recording pulses to: "+filename)
+
+ # Cause gr_file_sink object to accept new filename
+ # note use of self.prefix--filename prefix from
+ # command line (defaults to ./)
+ #
+ self.pulse_recording.open (self.prefix+filename)
+
+ #
+ # We open the header file as a regular file, write header data,
+ # then close
+ hdrf = open(self.prefix+hdrfilename, "w")
+ hdrf.write("receiver center frequency: "+str(self.frequency)+"\n")
+ hdrf.write("observing frequency: "+str(self.observing_freq)+"\n")
+ hdrf.write("DM: "+str(self.dm)+"\n")
+ hdrf.write("doppler: "+str(self.doppler)+"\n")
+ hdrf.write("pulse rate: "+str(self.pulse_freq)+"\n")
+ hdrf.write("pulse sps: "+str(self.pulse_freq*self.folding)+"\n")
+ hdrf.write("file sps: "+str(self.folder_input_rate)+"\n")
+
+ hdrf.write("sidereal: "+str(ephem.hours(sidtime))+"\n")
+ hdrf.write("bandwidth: "+str(self.u.adc_freq() / self.u.decim_rate())+"\n")
+ hdrf.write("sample type: short\n")
+ hdrf.write("sample size: 1\n")
+ hdrf.close()
+
+ # We get called at startup, and whenever the GUI "Set Folding params"
+ # button is pressed
+ #
+ def set_folding_params(self):
+ if (self.pulse_freq <= 0):
+ return
+
+ # Compute required sample rate
+ self.sample_rate = int(self.pulse_freq*self.folding)
+
+ # And the implied decimation rate
+ required_decimation = int(self.folder_input_rate / self.sample_rate)
+
+ # We also compute a new FFT comb filter, based on the expected
+ # spectral profile of our pulse parameters
+ #
+ # FFT-based comb filter
+ #
+ N_COMB_TAPS=int(self.sample_rate*4)
+ if N_COMB_TAPS > 2000:
+ N_COMB_TAPS = 2000
+ self.folder_comb_taps = Numeric.zeros(N_COMB_TAPS,Numeric.Complex64)
+ fincr = (self.sample_rate)/float(N_COMB_TAPS)
+ for i in range(0,len(self.folder_comb_taps)):
+ self.folder_comb_taps[i] = complex(0.0, 0.0)
+
+ freq = 0.0
+ harmonics = [1.0,2.0,3.0,4.0,5.0,6.0,7.0]
+ for i in range(0,len(self.folder_comb_taps)/2):
+ for j in range(0,len(harmonics)):
+ if abs(freq - harmonics[j]*self.pulse_freq) <= fincr:
+ self.folder_comb_taps[i] = complex(4.0, 0.0)
+ if harmonics[j] == 1.0:
+ self.folder_comb_taps[i] = complex(8.0, 0.0)
+ freq += fincr
+
+ if self.enable_comb_filter == True:
+ # Set the just-computed FFT comb filter taps
+ self.folder_comb.set_taps(self.folder_comb_taps)
+
+ # And compute a new decimated bandpass filter, to go in front
+ # of the comb. Primary function is to decimate and filter down
+ # to an exact-ish multiple of the target pulse rate
+ #
+ self.folding_taps = gr.firdes_band_pass (1.0, self.folder_input_rate,
+ 0.10, self.sample_rate/2, 10,
+ gr.firdes.WIN_HAMMING)
+
+ # Set the computed taps for the bandpass/decimate filter
+ self.folder_bandpass.set_taps (self.folding_taps)
+ #
+ # Record a spectral "hit" of a possible pulsar spectral profile
+ #
+ def record_hit(self,hits, hcavg, hcmax):
+ # Pick up current LMST
+ self.locality.date = ephem.now()
+ sidtime = self.locality.sidereal_time()
+
+ # Pick up localtime, for generating filenames
+ foo = time.localtime()
+
+ # Generate filenames for both data and header file
+ hitfilename = "%04d%02d%02d%02d.phit" % (foo.tm_year, foo.tm_mon,
+ foo.tm_mday, foo.tm_hour)
+
+ hitf = open(self.prefix+hitfilename, "a")
+ hitf.write("receiver center frequency: "+str(self.frequency)+"\n")
+ hitf.write("observing frequency: "+str(self.observing_freq)+"\n")
+ hitf.write("DM: "+str(self.dm)+"\n")
+ hitf.write("doppler: "+str(self.doppler)+"\n")
+
+ hitf.write("sidereal: "+str(ephem.hours(sidtime))+"\n")
+ hitf.write("bandwidth: "+str(self.u.adc_freq() / self.u.decim_rate())+"\n")
+ hitf.write("spectral peaks: "+str(hits)+"\n")
+ hitf.write("HCM: "+str(hcavg)+" "+str(hcmax)+"\n")
+ hitf.close()
+
+ # This is a callback used by ra_fftsink.py (passed on creation of
+ # ra_fftsink)
+ # Whenever the user moves the cursor within the FFT display, this
+ # shows the coordinate data
+ #
+ def xydfunc(self,xyv):
+ s = "%.6fHz\n%.3fdB" % (xyv[0], xyv[1])
+ if self.lowpass >= 500:
+ s = "%.6fHz\n%.3fdB" % (xyv[0]*1000, xyv[1])
+
+ self.myform['spec_data'].set_value(s)
+
+ # This is another callback used by ra_fftsink.py (passed on creation
+ # of ra_fftsink). We pass this as our "calibrator" function, but
+ # we create interesting side-effects in the GUI.
+ #
+ # This function finds peaks in the FFT output data, and reports
+ # on them through the "Best" text object in the GUI
+ # It also computes the Harmonic Compliance Measure (HCM), and displays
+ # that also.
+ #
+ def pulsarfunc(self,d,l):
+ x = range(0,l)
+ incr = float(self.lowpass)/float(l)
+ incr = incr * 2.0
+ bestdb = -50.0
+ bestfreq = 0.0
+ avg = 0
+ dcnt = 0
+ #
+ # First, we need to find the average signal level
+ #
+ for i in x:
+ if (i * incr) > self.lowest_freq and (i*incr) < (self.lowpass-2):
+ avg += d[i]
+ dcnt += 1
+ # Set average signal level
+ avg /= dcnt
+ s2=" "
+ findcnt = 0
+ #
+ # Then we find candidates that are greater than the user-supplied
+ # threshold.
+ #
+ # We try to cluster "hits" whose whole-number frequency is the
+ # same, and compute an average "hit" frequency.
+ #
+ lastint = 0
+ hits=[]
+ intcnt = 0
+ freqavg = 0
+ for i in x:
+ freq = i*incr
+ # If frequency within bounds, and the (dB-avg) value is above our
+ # threshold
+ if freq > self.lowest_freq and freq < self.lowpass-2 and (d[i] - avg) > self.threshold:
+ # If we're finding a new whole-number frequency
+ if lastint != int(freq):
+ # Record "center" of this hit, if this is a new hit
+ if lastint != 0:
+ s2 += "%5.3fHz " % (freqavg/intcnt)
+ hits.append(freqavg/intcnt)
+ findcnt += 1
+ lastint = int(freq)
+ intcnt = 1
+ freqavg = freq
+ else:
+ intcnt += 1
+ freqavg += freq
+ if (findcnt >= 14):
+ break
+
+ if intcnt > 1:
+ s2 += "%5.3fHz " % (freqavg/intcnt)
+ hits.append(freqavg/intcnt)
+
+ #
+ # Compute the HCM, by dividing each of the "hits" by each of the
+ # other hits, and comparing the difference between a "perfect"
+ # harmonic, and the observed frequency ratio.
+ #
+ measure = 0
+ max_measure=0
+ mcnt = 0
+ avg_dist = 0
+ acnt = 0
+ for i in range(1,len(hits)):
+ meas = hits[i]/hits[0] - int(hits[i]/hits[0])
+ if abs((hits[i]-hits[i-1])-hits[0]) < 0.1:
+ avg_dist += hits[i]-hits[i-1]
+ acnt += 1
+ if meas > 0.98 and meas < 1.0:
+ meas = 1.0 - meas
+ meas *= hits[0]
+ if meas >= max_measure:
+ max_measure = meas
+ measure += meas
+ mcnt += 1
+ if mcnt > 0:
+ measure /= mcnt
+ if acnt > 0:
+ avg_dist /= acnt
+ if len(hits) > 1:
+ measure /= mcnt
+ s3="\nHCM: Avg %5.3fHz(%d) Max %5.3fHz Dist %5.3fHz(%d)" % (measure,mcnt,max_measure, avg_dist, acnt)
+ if max_measure < 0.5 and len(hits) >= 2:
+ self.record_hit(hits, measure, max_measure)
+ self.avg_dist = avg_dist
+ else:
+ s3="\nHCM: --"
+ s4="\nAvg dB: %4.2f" % avg
+ self.myform['best_pulse'].set_value("("+s2+")"+s3+s4)
+
+ # Since we are nominally a calibrator function for ra_fftsink, we
+ # simply return what they sent us, untouched. A "real" calibrator
+ # function could monkey with the data before returning it to the
+ # FFT display function.
+ return(d)
+
+ #
+ # Callback for the "DM" gui object
+ #
+ # We call compute_dispfilter() as appropriate to compute a new filter,
+ # and then set that new filter into self.dispfilt.
+ #
+ def set_dm(self,dm):
+ self.dm = dm
+
+ ntaps = self.compute_disp_ntaps (self.dm, self.bw, self.observing_freq)
+ self.disp_taps = Numeric.zeros(ntaps, Numeric.Complex64)
+ self.compute_dispfilter(self.dm,self.doppler,self.bw,self.observing_freq)
+ self.dispfilt.set_taps(self.disp_taps)
+ self.myform['DM'].set_value(dm)
+ return(dm)
+
+ #
+ # Callback for the "Doppler" gui object
+ #
+ # We call compute_dispfilter() as appropriate to compute a new filter,
+ # and then set that new filter into self.dispfilt.
+ #
+ def set_doppler(self,doppler):
+ self.doppler = doppler
+
+ ntaps = self.compute_disp_ntaps (self.dm, self.bw, self.observing_freq)
+ self.disp_taps = Numeric.zeros(ntaps, Numeric.Complex64)
+ self.compute_dispfilter(self.dm,self.doppler,self.bw,self.observing_freq)
+ self.dispfilt.set_taps(self.disp_taps)
+ self.myform['Doppler'].set_value(doppler)
+ return(doppler)
+
+ #
+ # Compute a de-dispersion filter
+ # From Hankins, et al, 1975
+ #
+ # This code translated from dedisp_filter.c from Swinburne
+ # pulsar software repository
+ #
+ def compute_dispfilter(self,dm,doppler,bw,centerfreq):
+ npts = len(self.disp_taps)
+ tmp = Numeric.zeros(npts, Numeric.Complex64)
+ M_PI = 3.14159265358
+ DM = dm/2.41e-10
+
+ #
+ # Because astronomers are a crazy bunch, the "standard" calcultion
+ # is in Mhz, rather than Hz
+ #
+ centerfreq = centerfreq / 1.0e6
+ bw = bw / 1.0e6
+
+ isign = int(bw / abs (bw))
+
+ # Center frequency may be doppler shifted
+ cfreq = centerfreq / doppler
+
+ # As well as the bandwidth..
+ bandwidth = bw / doppler
+
+ # Bandwidth divided among bins
+ binwidth = bandwidth / npts
+
+ # Delay is an "extra" parameter, in usecs, and largely
+ # untested in the Swinburne code.
+ delay = 0.0
+
+ # This determines the coefficient of the frequency response curve
+ # Linear in DM, but quadratic in center frequency
+ coeff = isign * 2.0*M_PI * DM / (cfreq*cfreq)
+
+ # DC to nyquist/2
+ n = 0
+ for i in range(0,int(npts/2)):
+ freq = (n + 0.5) * binwidth
+ phi = coeff*freq*freq/(cfreq+freq) + (2.0*M_PI*freq*delay)
+ tmp[i] = complex(math.cos(phi), math.sin(phi))
+ n += 1
+
+ # -nyquist/2 to DC
+ n = int(npts/2)
+ n *= -1
+ for i in range(int(npts/2),npts):
+ freq = (n + 0.5) * binwidth
+ phi = coeff*freq*freq/(cfreq+freq) + (2.0*M_PI*freq*delay)
+ tmp[i] = complex(math.cos(phi), math.sin(phi))
+ n += 1
+
+ self.disp_taps = FFT.inverse_fft(tmp)
+ return(self.disp_taps)
+
+ #
+ # Compute minimum number of taps required in de-dispersion FFT filter
+ #
+ def compute_disp_ntaps(self,dm,bw,freq):
+ #
+ # Dt calculations are in Mhz, rather than Hz
+ # crazy astronomers....
+ mbw = bw/1.0e6
+ mfreq = freq/1.0e6
+
+ f_lower = mfreq-(mbw/2)
+ f_upper = mfreq+(mbw/2)
+
+ # Compute smear time
+ Dt = dm/2.41e-4 * (1.0/(f_lower*f_lower)-1.0/(f_upper*f_upper))
+
+ # ntaps is now bandwidth*smeartime
+ # Should be bandwidth*smeartime*2, but the Gnu Radio FFT filter
+ # already expands it by a factor of 2
+ ntaps = bw*Dt
+ if ntaps < 64:
+ ntaps = 64
+ return(int(ntaps))
+
+def main ():
+ app = stdgui.stdapp(app_flow_graph, "RADIO ASTRONOMY PULSAR RECEIVER: $Revision$", nstatus=1)
+ app.MainLoop()
+
+if __name__ == '__main__':
+ main ()