#!/usr/bin/python3
#
# Copyright 2015-2016,2018 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.
#
"""
UHD FFT: Simple Spectrum Analyzer for UHD.
"""

# Original GRC header:
##################################################
# GNU Radio Python Flow Graph
# Title: UHD FFT
# Author: Example
# Description: UHD FFT Waveform Plotter
# Generated: Thu Jun 18 13:57:09 2015
##################################################
# Note this is a heavily modified version of a
# the uhd_fft.grc example.

from __future__ import print_function
from __future__ import division
import ctypes
import sys
import sip
import threading
import time
from distutils.version import StrictVersion
from PyQt5 import Qt
from gnuradio import eng_notation
from gnuradio import eng_arg
from gnuradio import gr
from gnuradio import qtgui
from gnuradio import uhd
from gnuradio.filter import firdes
from gnuradio.qtgui import Range, RangeWidget
try:
    from uhd_app import UHDApp
except ImportError:
    from gnuradio.uhd.uhd_app import UHDApp


class uhd_fft(UHDApp, gr.top_block, Qt.QWidget):
    """
    Simple UHD Spectrum Analyzer / Scope App.
    """
    def __init__(self, args):
        UHDApp.__init__(self, args=args, prefix="UHD FFT")
        gr.top_block.__init__(self, "UHD FFT")
        Qt.QWidget.__init__(self)
        ##################################################
        # Init QT App
        ##################################################
        self.setWindowTitle("UHD FFT")
        try:
            self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))
        except:
            pass
        self.top_scroll_layout = Qt.QVBoxLayout()
        self.setLayout(self.top_scroll_layout)
        self.top_scroll = Qt.QScrollArea()
        self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)
        self.top_scroll_layout.addWidget(self.top_scroll)
        self.top_scroll.setWidgetResizable(True)
        self.top_widget = Qt.QWidget()
        self.top_scroll.setWidget(self.top_widget)
        self.top_layout = Qt.QVBoxLayout(self.top_widget)
        self.top_grid_layout = Qt.QGridLayout()
        self.top_layout.addLayout(self.top_grid_layout)
        self.settings = Qt.QSettings("GNU Radio", "uhd_fft")
        geo_settings = self.settings.value("geometry")
        if geo_settings:
            self.restoreGeometry(self.settings.value("geometry"))

        ##################################################
        # Parameters
        ##################################################
        self.antenna = args.antenna
        self.args = args
        self.fft_size = args.fft_size
        self.freq = args.freq
        self.gain = args.gain
        self.gain_type = args.gain_type
        self.samp_rate = args.samp_rate
        self.spec = args.spec
        self.stream_args = args.stream_args
        self.update_rate = args.update_rate
        self.wire_format = args.otw_format
        self.lo_offset = args.lo_offset

        ##################################################
        # Variables
        ##################################################
        self.chan0_lo_locked = chan0_lo_locked = uhd.sensor_value("", False, "")
        self.usrp_device_info = usrp_device_info = "[No USRP Device Info Found!]"
        self.uhd_version_info = uhd_version_info = uhd.get_version_string()
        self.lo_locked_probe = lo_locked_probe = chan0_lo_locked.to_bool()
        self.fft_average = 1.0
        if args.avg_alpha is not None:
            if args.avg_alpha < 0.0 or args.avg_alpha > 1.0:
                print("[UHD FFT] [ERROR] Alpha must be in [0.0, 1.0]!")
                exit(1)
            self.fft_average = args.avg_alpha
        else:
            fft_average_values = {
                'off': 1.0,
                'low': 0.2,
                'medium': 0.1,
                'high': 0.05
            }
            if args.fft_average in fft_average_values.keys():
                self.fft_average = fft_average_values[args.fft_average]

        ##################################################
        # Blocks
        ##################################################
        self.setup_usrp(uhd.usrp_source, args)
        self._ant_options = self.usrp.get_antennas(0)
        for c in range(len(self.channels)):
            self.usrp.set_bandwidth(self.samp_rate + abs(self.lo_offset), c)
        self.usrp_device_info = self.get_usrp_info_string(compact=True, tx_or_rx='rx')

        ### Now set up the GUI widgets: #####################################
        # Sampling rate:
        self._samp_rate__tool_bar = Qt.QToolBar(self)
        self._samp_rate__tool_bar.addWidget(Qt.QLabel("Sampling Rate: "))
        self._samp_rate__line_edit = Qt.QLineEdit(eng_notation.num_to_str(self.samp_rate))
        self._samp_rate__tool_bar.addWidget(self._samp_rate__line_edit)
        self._samp_rate__line_edit.returnPressed.connect(
            lambda: self.set_samp_rate(eng_notation.str_to_num(str(self._samp_rate__line_edit.text()))))
        self.top_grid_layout.addWidget(self._samp_rate__tool_bar, 3, 2, 1, 2)
        # Gain:
        self._gain__range = Range(
            self.gain_range.start(),
            self.gain_range.stop(),
            max(self.gain_range.step(), 1.0),
            self.gain,
            200
        )
        self._gain__win = RangeWidget(self._gain__range, self.set_gain, "RX Gain", "counter_slider", float)
        self.top_grid_layout.addWidget(self._gain__win, 2,0,1,4)
        # Center frequency:
        self._freq_tool_bar = Qt.QToolBar(self)
        self._freq_tool_bar.addWidget(Qt.QLabel("RX Tune Frequency: "))
        self._freq_line_edit = Qt.QLineEdit(eng_notation.num_to_str(self.freq))
        self._freq_tool_bar.addWidget(self._freq_line_edit)
        self._freq_line_edit.returnPressed.connect(
            lambda: self.set_freq_qt(eng_notation.str_to_num(str(self._freq_line_edit.text()))))
        self.top_grid_layout.addWidget(self._freq_tool_bar, 3,0,1,2)
        # Antenna Selection:
        self._ant_labels = self._ant_options
        self._ant_tool_bar = Qt.QToolBar(self)
        self._ant_tool_bar.addWidget(Qt.QLabel("Antenna: "))
        self._ant_combo_box = Qt.QComboBox()
        self._ant_tool_bar.addWidget(self._ant_combo_box)
        for label in self._ant_labels: self._ant_combo_box.addItem(label)
        self._ant_callback = lambda i: Qt.QMetaObject.invokeMethod(self._ant_combo_box, "setCurrentIndex", Qt.Q_ARG("int", self._ant_options.index(i)))
        self._ant_callback(self.antenna)
        self._ant_combo_box.currentIndexChanged.connect(lambda i: self.set_ant(self._ant_options[i]))
        self.top_grid_layout.addWidget(self._ant_tool_bar, 4,2,1,2)
        # Device + UHD info:
        self._usrp_device_info_tool_bar = Qt.QToolBar(self)
        self._usrp_device_info_formatter = lambda x: x
        self._usrp_device_info_tool_bar.addWidget(Qt.QLabel("Device Information: "))
        self._usrp_device_info_label = Qt.QLabel(str(self._usrp_device_info_formatter(self.usrp_device_info)))
        self._usrp_device_info_tool_bar.addWidget(self._usrp_device_info_label)
        self.top_grid_layout.addWidget(self._usrp_device_info_tool_bar, 1,2,1,2)
        self._uhd_version_info_tool_bar = Qt.QToolBar(self)
        self._uhd_version_info_formatter = lambda x: x
        self._uhd_version_info_tool_bar.addWidget(Qt.QLabel("UHD Version: "))
        self._uhd_version_info_label = Qt.QLabel(str(self._uhd_version_info_formatter(self.uhd_version_info)))
        self._uhd_version_info_tool_bar.addWidget(self._uhd_version_info_label)
        self.top_grid_layout.addWidget(self._uhd_version_info_tool_bar, 1,0,1,2)
        ### Plot GUIs #######################################################
        widths = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
        colors = ["blue", "red", "green", "cyan", "magenta", "black", "yellow", "dark red", "dark green", "dark blue"]
        styles = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
        markers = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
        alphas = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
        self.display = Qt.QTabWidget()
        self.display_widget_0 = Qt.QWidget()
        self.display_layout_0 = Qt.QBoxLayout(Qt.QBoxLayout.TopToBottom, self.display_widget_0)
        self.display_grid_layout_0 = Qt.QGridLayout()
        self.display_layout_0.addLayout(self.display_grid_layout_0)
        self.display.addTab(self.display_widget_0, "Spectrum")
        self.display_widget_1 = Qt.QWidget()
        self.display_layout_1 = Qt.QBoxLayout(Qt.QBoxLayout.TopToBottom, self.display_widget_1)
        self.display_grid_layout_1 = Qt.QGridLayout()
        self.display_layout_1.addLayout(self.display_grid_layout_1)
        self.display.addTab(self.display_widget_1, "Waterfall")
        self.display_widget_2 = Qt.QWidget()
        self.display_layout_2 = Qt.QBoxLayout(Qt.QBoxLayout.TopToBottom, self.display_widget_2)
        self.display_grid_layout_2 = Qt.QGridLayout()
        self.display_layout_2.addLayout(self.display_grid_layout_2)
        self.display.addTab(self.display_widget_2, "Scope")
        self.top_grid_layout.addWidget(self.display, 0,0,1,4)
        self.qtgui_waterfall_sink_x_0 = qtgui.waterfall_sink_c(
            self.fft_size, #size
            firdes.WIN_BLACKMAN_hARRIS, #wintype
            self.freq, #fc
            self.samp_rate, #bw
            "", #name
            len(self.channels) #number of inputs
        )
        self.qtgui_waterfall_sink_x_0.set_update_time(self.update_rate)
        self.qtgui_waterfall_sink_x_0.enable_grid(False)
        self.qtgui_waterfall_sink_x_0.disable_legend()
        alpha = 10.**(len(self.channels)-1)
        for i in range(len(self.channels)):
            self.qtgui_waterfall_sink_x_0.set_line_label(i, "Channel {0}".format(i))
            self.qtgui_waterfall_sink_x_0.set_color_map(i, 0)
            self.qtgui_waterfall_sink_x_0.set_line_alpha(i, alpha)
        self.qtgui_waterfall_sink_x_0.set_intensity_range(-90, 10)
        self._qtgui_waterfall_sink_x_0_win = sip.wrapinstance(self.qtgui_waterfall_sink_x_0.pyqwidget(), Qt.QWidget)
        self.display_grid_layout_1.addWidget(self._qtgui_waterfall_sink_x_0_win, 0,0,1,4)
        self.qtgui_time_sink_x_0 = qtgui.time_sink_c(
                1024, #size
                self.samp_rate, #samp_rate
                "", #name
                len(self.channels) #number of inputs
        )
        self.qtgui_time_sink_x_0.set_update_time(self.update_rate)
        self.qtgui_time_sink_x_0.set_y_axis(-1, 1)
        self.qtgui_time_sink_x_0.set_y_label("Amplitude", "")
        self.qtgui_time_sink_x_0.enable_tags(0, True)
        self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")
        self.qtgui_time_sink_x_0.enable_autoscale(True)
        self.qtgui_time_sink_x_0.enable_grid(False)
        self.qtgui_time_sink_x_0.enable_control_panel(True)
        self.qtgui_time_sink_x_0.disable_legend()
        for i in range(2*len(self.channels)):
            if(i % 2 == 0):
                self.qtgui_time_sink_x_0.set_line_label(i, "Re{{Channel {0}}}".format(i//2))
            else:
                self.qtgui_time_sink_x_0.set_line_label(i, "Im{{Channel {0}}}".format(i//2))
            self.qtgui_time_sink_x_0.set_line_width(i, widths[i])
            self.qtgui_time_sink_x_0.set_line_color(i, colors[i])
            self.qtgui_time_sink_x_0.set_line_style(i, styles[i])
            self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])
            self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])
        self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)
        self.display_grid_layout_2.addWidget(self._qtgui_time_sink_x_0_win, 0,0,1,4)
        self.qtgui_freq_sink_x_0 = qtgui.freq_sink_c(
                self.fft_size, #size
                firdes.WIN_BLACKMAN_hARRIS, #wintype
                self.freq, #fc
                self.samp_rate, #bw
                "", #name
                len(self.channels) #number of inputs
        )
        self.qtgui_freq_sink_x_0.set_update_time(self.update_rate)
        self.qtgui_freq_sink_x_0.set_y_axis(-100, 10)
        self.qtgui_freq_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, 0.0, 0, "")
        self.qtgui_freq_sink_x_0.enable_autoscale(True)
        self.qtgui_freq_sink_x_0.enable_grid(True)
        self.qtgui_freq_sink_x_0.set_fft_average(self.fft_average)
        self.qtgui_freq_sink_x_0.enable_control_panel(True)
        self.qtgui_freq_sink_x_0.disable_legend()
        for i in range(len(self.channels)):
            self.qtgui_freq_sink_x_0.set_line_label(i, "Channel {0}".format(i))
            self.qtgui_freq_sink_x_0.set_line_width(i, widths[i])
            self.qtgui_freq_sink_x_0.set_line_color(i, colors[i])
            self.qtgui_freq_sink_x_0.set_line_alpha(i, alphas[i])
        self._qtgui_freq_sink_x_0_win = sip.wrapinstance(self.qtgui_freq_sink_x_0.pyqwidget(), Qt.QWidget)
        self.display_grid_layout_0.addWidget(self._qtgui_freq_sink_x_0_win, 0,0,1,4)
        def _freeze_scaling(widget, sleep_time):
            time.sleep(sleep_time)
            widget.enable_autoscale(False)
        _freeze_fft_thread = threading.Thread(target=lambda: _freeze_scaling(self.qtgui_freq_sink_x_0, .5/self.fft_average))
        _freeze_fft_thread.daemon = True
        _freeze_fft_thread.start()
        _freeze_scope_thread = threading.Thread(target=lambda: _freeze_scaling(self.qtgui_time_sink_x_0, 2.0))
        _freeze_scope_thread.daemon = True
        _freeze_scope_thread.start()
        if args.phase_relations and len(self.channels) > 1:
            self.display_widget_phase = Qt.QWidget()
            self.display_layout_phase = Qt.QBoxLayout(Qt.QBoxLayout.TopToBottom, self.display_widget_phase)
            self.display_grid_layout_phase = Qt.QGridLayout()
            self.display_layout_phase.addLayout(self.display_grid_layout_phase)
            self.display.addTab(self.display_widget_phase, "Rel. Phase")
            self.qtgui_phase_plot = qtgui.time_sink_f(
                    1024, #size
                    self.samp_rate, #samp_rate
                    "", #name
                    len(self.channels) - 1
            )
            self.qtgui_phase_plot.set_update_time(self.update_rate)
            self.qtgui_phase_plot.set_y_axis(-3.5, 3.5)
            self.qtgui_phase_plot.set_y_label("Relative Phase", "")
            self.qtgui_phase_plot.enable_tags(0, True)
            self.qtgui_phase_plot.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")
            self.qtgui_phase_plot.enable_autoscale(False)
            self.qtgui_phase_plot.enable_grid(True)
            self.qtgui_phase_plot.enable_control_panel(True)
            self.qtgui_phase_plot.disable_legend()
            for i in range(len(self.channels) - 1):
                self.qtgui_phase_plot.set_line_label(i, "Phase Delta Channels {0}/{1}".format(i, i+1))
                self.qtgui_phase_plot.set_line_width(i, widths[i])
                self.qtgui_phase_plot.set_line_color(i, colors[i])
                self.qtgui_phase_plot.set_line_style(i, styles[i])
                self.qtgui_phase_plot.set_line_marker(i, markers[i])
                self.qtgui_phase_plot.set_line_alpha(i, alphas[i])
            self._qtgui_phase_plot_win = sip.wrapinstance(self.qtgui_phase_plot.pyqwidget(), Qt.QWidget)
            self.display_grid_layout_phase.addWidget(self._qtgui_phase_plot_win, 0,0,1,4)
        ### Other widgets ###################################################
        self._lo_locked_probe_tool_bar = Qt.QToolBar(self)
        self._lo_locked_probe_formatter = lambda x: {True: 'Yes', False: 'No'}[x]
        if self.has_lo_sensor:
            self._lo_locked_probe_tool_bar.addWidget(Qt.QLabel("LO locked: "))
            self._lo_locked_probe_label = Qt.QLabel(str(self._lo_locked_probe_formatter(self.lo_locked_probe)))
        else:
            self._lo_locked_probe_tool_bar.addWidget(Qt.QLabel("No LO lock sensor available."))
            self._lo_locked_probe_label = Qt.QLabel("")
        self._lo_locked_probe_tool_bar.addWidget(self._lo_locked_probe_label)
        self.top_grid_layout.addWidget(self._lo_locked_probe_tool_bar, 4,0,1,2)
        def _current_freq_probe():
            while True:
                val = self.usrp.get_center_freq(0)
                try:
                    if val != self.freq:
                        self.set_freq_qt(val, tune_source='freqsink_msg')
                except AttributeError:
                    pass
                time.sleep(1.0 / (10))
        _current_freq_thread = threading.Thread(target=_current_freq_probe)
        _current_freq_thread.daemon = True
        _current_freq_thread.start()
        def _chan0_lo_locked_probe():
            while self.has_lo_sensor:
                val = self.usrp.get_sensor('lo_locked')
                try:
                    self.set_chan0_lo_locked(val)
                except AttributeError:
                    pass
                time.sleep(1.0 / (10))
        _chan0_lo_locked_thread = threading.Thread(target=_chan0_lo_locked_probe)
        _chan0_lo_locked_thread.daemon = True
        _chan0_lo_locked_thread.start()

        ##################################################
        # Connections
        ##################################################
        # allows double clicking in the freq plot
        self.msg_connect((self.qtgui_freq_sink_x_0, 'freq'), (self.qtgui_freq_sink_x_0, 'freq'))
        self.msg_connect((self.qtgui_freq_sink_x_0, 'freq'), (self.qtgui_waterfall_sink_x_0, 'freq'))
        self.msg_connect((self.qtgui_freq_sink_x_0, 'freq'), (self.usrp, 'command'))
        # allows double clicking in the waterfall plot
        self.msg_connect((self.qtgui_waterfall_sink_x_0, 'freq'), (self.qtgui_waterfall_sink_x_0, 'freq'))
        self.msg_connect((self.qtgui_waterfall_sink_x_0, 'freq'), (self.qtgui_freq_sink_x_0, 'freq'))
        self.msg_connect((self.qtgui_waterfall_sink_x_0, 'freq'), (self.usrp, 'command'))
        for idx in range(len(self.channels)):
            self.connect((self.usrp, idx), (self.qtgui_freq_sink_x_0, idx))
            self.connect((self.usrp, idx), (self.qtgui_time_sink_x_0, idx))
            self.connect((self.usrp, idx), (self.qtgui_waterfall_sink_x_0, idx))
        if args.phase_relations and len(self.channels) > 1:
            for idx in range(len(self.channels[:-1])):
                self.connect_phase_plot(
                        (self.usrp, idx),
                        (self.usrp, idx+1),
                        (self.qtgui_phase_plot, idx)
                )

    def connect_phase_plot(self, src_port1, src_port2, dst_port):
        " Calculate relative phase between two src ports and send it dst_port "
        from gnuradio import blocks
        multiplier = blocks.multiply_cc()
        self.connect(src_port1, (multiplier, 0), blocks.complex_to_arg(), dst_port)
        self.connect(src_port2, blocks.conjugate_cc(), (multiplier, 1))

    def closeEvent(self, event):
        self.settings = Qt.QSettings("GNU Radio", "uhd_fft")
        self.settings.setValue("geometry", self.saveGeometry())
        event.accept()

    def set_antenna(self, antenna):
        self.antenna = antenna
        self.set_ant(self.antenna)

    def set_args(self, args):
        self.args = args

    def set_fft_size(self, fft_size):
        self.fft_size = fft_size

    def set_freq_qt(self, freq, tune_source='textbox'):
        if freq == self.freq:
            return
        if tune_source == 'textbox':
            # This sets self.freq:
            self.set_freq(freq, skip_sync=False)
        elif tune_source == 'freqsink_msg':
            self.freq = freq
        else:
            raise RuntimeError("Invalid tune source: {src}".format(tune_source))
        Qt.QMetaObject.invokeMethod(
                self._freq_line_edit, "setText",
                Qt.Q_ARG("QString", eng_notation.num_to_str(self.freq))
        )
        self.qtgui_waterfall_sink_x_0.set_frequency_range(self.freq, self.samp_rate)
        if tune_source != 'freqsink_msg':
            self.qtgui_freq_sink_x_0.set_frequency_range(self.freq, self.samp_rate)

    def set_update_rate(self, update_rate):
        self.update_rate = update_rate
        self.qtgui_freq_sink_x_0.set_update_time(self.update_rate)
        self.qtgui_time_sink_x_0.set_update_time(self.update_rate)
        self.qtgui_waterfall_sink_x_0.set_update_time(self.update_rate)

    def set_chan0_lo_locked(self, chan0_lo_locked):
        if not self.has_lo_sensor:
            return
        self.chan0_lo_locked = chan0_lo_locked
        self.set_lo_locked_probe(self._lo_locked_probe_formatter(self.chan0_lo_locked.to_bool()))

    def set_usrp_device_info(self, usrp_device_info):
        self.usrp_device_info = usrp_device_info
        Qt.QMetaObject.invokeMethod(self._usrp_device_info_label, "setText", Qt.Q_ARG("QString", repr(self.usrp_device_info)))

    def set_uhd_version_info(self, uhd_version_info):
        self.uhd_version_info = uhd_version_info
        Qt.QMetaObject.invokeMethod(self._uhd_version_info_label, "setText", Qt.Q_ARG("QString", str(self.uhd_version_info)))

    def set_samp_rate(self, samp_rate):
        self.vprint("Setting sampling rate to: {} MHz".format(samp_rate / 1e6))
        self.samp_rate = samp_rate
        Qt.QMetaObject.invokeMethod(
                self._samp_rate__line_edit, "setText",
                Qt.Q_ARG("QString", eng_notation.num_to_str(self.samp_rate))
        )
        self.qtgui_freq_sink_x_0.set_frequency_range(self.freq, self.samp_rate)
        self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)
        self.qtgui_waterfall_sink_x_0.set_frequency_range(self.freq, self.samp_rate)
        self.usrp.set_samp_rate(self.samp_rate)
        for c in range(len(self.channels)):
            self.usrp.set_bandwidth(self.samp_rate + abs(self.lo_offset), c)

    def set_lo_locked_probe(self, lo_locked_probe):
        self.lo_locked_probe = lo_locked_probe
        Qt.QMetaObject.invokeMethod(self._lo_locked_probe_label, "setText", Qt.Q_ARG("QString", str(self.lo_locked_probe)))

    def set_ant(self, ant):
        self.antenna = ant
        self._ant_callback(self.antenna)
        for c in range(len(self.channels)):
            self.usrp.set_antenna(self.antenna, c)


def setup_argparser():
    """
    Create argument parser for UHD FFT.
    """
    parser = UHDApp.setup_argparser(
            description="UHD FFT",
            tx_or_rx="Rx",
    )
    group = parser.add_argument_group('UHD FFT Arguments')
    group.add_argument("--fft-size", type=eng_arg.intx, default=1024,
        help="Set number of FFT bins")
    group.add_argument("--fft-average", default='medium', choices=('off', 'low', 'medium', 'high'),
        help="Set FFT averaging")
    group.add_argument("--avg-alpha", type=float, default=None,
        help="Specify FFT average alpha (overrides --fft-average)")
    group.add_argument("--update-rate", dest="update_rate", type=eng_arg.eng_float, default=eng_notation.num_to_str(.1),
        help="Set GUI widget update period in seconds")
    group.add_argument("--phase-relations", action="store_true",
        help="Plot relative phases between multiple channels")
    return parser


def main():
    """
    Go, go, go!
    """
    args = setup_argparser().parse_args()
    qapp = Qt.QApplication(sys.argv)
    tb = uhd_fft(args)
    tb.start()
    tb.show()
    def quitting():
        """
        Action ('Slot' in Qt lingo) to close the flow graph when the Qt window
        is closed.
        """
        print("\nStopping flowgraph...")
        tb.stop()
        tb.wait()
    qapp.aboutToQuit.connect(quitting)
    qapp.exec_()
    tb = None #to clean up Qt widgets

if __name__ == '__main__':
    if sys.platform.startswith('linux'):
        try:
            x11 = ctypes.cdll.LoadLibrary('libX11.so')
            x11.XInitThreads()
        except:
            print("Warning: failed to XInitThreads()")
    main()
