diff options
Diffstat (limited to 'gr-radio-astronomy/src/python/usrp_psr_receiver.py')
-rwxr-xr-x | gr-radio-astronomy/src/python/usrp_psr_receiver.py | 1096 |
1 files changed, 0 insertions, 1096 deletions
diff --git a/gr-radio-astronomy/src/python/usrp_psr_receiver.py b/gr-radio-astronomy/src/python/usrp_psr_receiver.py deleted file mode 100755 index 6ce4325a2..000000000 --- a/gr-radio-astronomy/src/python/usrp_psr_receiver.py +++ /dev/null @@ -1,1096 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2004,2005,2007 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. -# - - -# -# -# Pulsar receiver application -# -# Performs both harmonic folding analysis -# and epoch folding analysis -# -# -from gnuradio import gr, gru, blks2, audio -from usrpm import usrp_dbid -from gnuradio import usrp, optfir -from gnuradio import eng_notation -from gnuradio.eng_option import eng_option -from gnuradio.wxgui import stdgui2, ra_fftsink, ra_stripchartsink, form, slider -from optparse import OptionParser -import wx -import sys -import Numeric -import numpy.fft -import ephem -import time -import os -import math - - -class app_flow_graph(stdgui2.std_top_block): - def __init__(self, frame, panel, vbox, argv): - stdgui2.std_top_block.__init__(self, frame, panel, vbox, argv) - - 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") - parser.add_option("-A", "--audio_source", default="plughw:0,0", help="Audio input device spec") - parser.add_option("-N", "--num_pulses", default=1, type="eng_float", help="Number of display pulses") - (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 - self.audiodev = options.audio_source - self.mult = int(options.num_pulses) - - # 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 N pulses in the pulse viewer window - FOLD_MULT=self.mult - - # determine the daughterboard subdevice we're using - self.subdev = usrp.selected_subdev(self.u, options.rx_subdev_spec) - self.cardtype = self.u.daughterboard_id(0) - - # Compute raw input rate - input_rate = self.u.adc_freq() / self.u.decim_rate() - - # BW==input_rate for complex data - self.bw = input_rate - - # - # Set baseband filter bandwidth if DBS_RX: - # - if self.cardtype == usrp_dbid.DBS_RX: - lbw = input_rate / 2 - if lbw < 1.0e6: - lbw = 1.0e6 - self.subdev.set_bw(lbw) - - # - # 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 (panel, - fft_size=int(options.fft_size), sample_rate=PULSAR_MAX_FREQ*2, - title="Post-detector spectrum", - ofunc=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)) - times = " %d Pulse Intervals" % self.mult - self.chart = ra_stripchartsink.stripchart_sink_f (panel, - sample_rate=1, - stripsize=self.folding*FOLD_MULT, parallel=True, title="Pulse Profiles: "+hz+per+times, - 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 - # - #print "input_rate ", second_input_rate, "audiodev ", self.audiodev - #self.audio = audio.sink(second_input_rate, self.audiodev) - - # - # 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) - - # Detector - self.detector = gr.complex_to_mag_squared() - - 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 = blks2.rational_resampler_fff(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, detector - self.connect(self.u, self.dispfilt, self.detector) - - # Connect detector output to FIR LPF - # in two stages, followed by the FFT scope - self.connect(self.detector, 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 = numpy.fft.ifft(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 = stdgui2.stdapp(app_flow_graph, "RADIO ASTRONOMY PULSAR RECEIVER: $Revision$", nstatus=1) - app.MainLoop() - -if __name__ == '__main__': - main () |