diff options
author | Matthias P. Braendli <matthias.braendli@mpb.li> | 2017-12-29 09:30:47 +0100 |
---|---|---|
committer | Matthias P. Braendli <matthias.braendli@mpb.li> | 2017-12-29 09:30:47 +0100 |
commit | 0c0f828c6bccee3aeb3049cb8b5bb480153cd3b6 (patch) | |
tree | 520dc4ff15dbc8dba056ea03d762d570b243f27d /dpd | |
parent | 9234155749be0c9ee3ae1269f47c2240d302c21a (diff) | |
parent | 8e3338479c180418a05ab030c60ba01c2a8615ca (diff) | |
download | dabmod-0c0f828c6bccee3aeb3049cb8b5bb480153cd3b6.tar.gz dabmod-0c0f828c6bccee3aeb3049cb8b5bb480153cd3b6.tar.bz2 dabmod-0c0f828c6bccee3aeb3049cb8b5bb480153cd3b6.zip |
Merge branch 'next' into outputRefactoring
Diffstat (limited to 'dpd')
-rw-r--r-- | dpd/README.md | 73 | ||||
-rwxr-xr-x | dpd/apply_adapt_dumps.py | 147 | ||||
-rw-r--r-- | dpd/dpd.ini | 2 | ||||
-rw-r--r-- | dpd/environment.yml | 134 | ||||
-rwxr-xr-x | dpd/main.py | 109 | ||||
-rw-r--r-- | dpd/src/Adapt.py | 35 | ||||
-rw-r--r-- | dpd/src/Dab_Util.py | 26 | ||||
-rw-r--r-- | dpd/src/ExtractStatistic.py | 8 | ||||
-rw-r--r-- | dpd/src/GlobalConfig.py (renamed from dpd/src/Const.py) | 39 | ||||
-rw-r--r-- | dpd/src/MER.py | 13 | ||||
-rw-r--r-- | dpd/src/Measure.py | 7 | ||||
-rw-r--r-- | dpd/src/Measure_Shoulders.py | 8 | ||||
-rw-r--r-- | dpd/src/Model.py | 6 | ||||
-rw-r--r-- | dpd/src/Model_AM.py | 7 | ||||
-rw-r--r-- | dpd/src/Model_Lut.py | 4 | ||||
-rw-r--r-- | dpd/src/Model_PM.py | 7 | ||||
-rw-r--r-- | dpd/src/Model_Poly.py | 6 | ||||
-rw-r--r-- | dpd/src/RX_Agc.py | 7 | ||||
-rw-r--r-- | dpd/src/Symbol_align.py | 10 | ||||
-rw-r--r-- | dpd/src/TX_Agc.py | 3 | ||||
-rw-r--r-- | dpd/src/phase_align.py | 6 | ||||
-rwxr-xr-x | dpd/src/subsample_align.py | 10 |
22 files changed, 211 insertions, 456 deletions
diff --git a/dpd/README.md b/dpd/README.md index 24611d9..fc84bad 100644 --- a/dpd/README.md +++ b/dpd/README.md @@ -81,50 +81,66 @@ essential that there is no nonlinearity in the RX path! Software Setup -------------- -We assume that you already installed *ODR-DabMux* and *ODR-DabMod*. In order to -satisfy dependencies for the predistortion, you can install all required python -modules using *conda*. To obtain the *conda* command line tool, install -[miniconda](https://conda.io/docs/user-guide/install/linux.html) and do the -beginners tutorial. It helps you keep the global python environment clean and -install the exact same package versions as we used for development. - -``` -conda env create -f dpd/environment.yml -source activate dab -``` - -Alternatively you can also install the dependencies from your distribution. -You will need at least scipy, matplotlib and python-zeromq, and maybe more. - +We assume that you already installed *ODR-DabMux* and *ODR-DabMod*. +You should install the required python dependencies for the DPDCE using +distribution packages. You will need at least scipy, matplotlib and +python-zeromq. Use the predistortion ---------------------- -Run the multiplexer and the modulator: +Make sure you have a ODR-DabMux running with a TCP output on port 9200. + +Then run the modulator, with the example dpd configuration file. ``` -ODR-DabMux/src/odr-dabmux ../simple.mux -ODR-DabMod/odr-dabmod dpd/dpd.ini +./odr-dabmod dpd/dpd.ini ``` +This configuration file is different from usual defaults in several respects: + + * logging to /tmp/dabmod.log + * 4x oversampling: 8192000 sample rate + * a very small digital gain, which will be overridden by the DPDCE + * predistorter enabled + * UHD output with a rather low TX gain, which will be overridden by DPDCE + The DPDCE uses automatic gain control for both TX and RX gain to get both a high quantisation quality for the most frequent amplitude regions and a high enough back-off so the peaks are also quantised correctly. This means that the output power will stay at the same level, but the DPDCE may change TX gain to -trade it with digital gain and also change RX gain. - -As a first test you should run the DPDCE with the *--plot* parameter. It -preserves the output power and generates all available visualisation plots in -the newly created logging directory `/tmp/dpd_<time_stamp>`. As the predistortion -should increase the peak to shoulder ratio, you should select a *txgain* in the -ODR-DabMod configuration file such that the initial peak-to-soulder ratio -visible on your spectrum analyser. This way, you will be able to see a the +trade it with digital gain and also change RX gain. This also implies that you +should *not modify txgain, rxgain, digital gain or coefficient settings manually!* +When the DPDCE is used, it controls these settings, and there are command line +options for you to define initial values. + +To verify that the communication between the DPDCE and ODR-DabMod is ok, +you can use the status and reset options: + +``` +cd dpd +python main.py --status +python main.py --reset +``` + +The reset option sets all DPD-related settings to the defaults (as shown in the +`--help` usage screen) and stops. + +When neither `--status` nor `--reset` is given, the DPDCE will run the predistortion +algorithm. As a first test you should run the DPDCE with the `--plot` +parameter. It preserves the output power and generates all available +visualisation plots in the newly created logging directory +`/tmp/dpd_<time_stamp>`. As the predistortion should increase the peak to +shoulder ratio, you should select a *txgain* in the ODR-DabMod configuration +file such that the initial peak-to-soulder ratio visible on your spectrum +analyser. This way, you will be able to see a the change. ``` cd dpd python main.py --plot ``` + The DPDCE now does 10 iterations, and tries to improve the predistortion effectiveness. In each step the learning rate is decreased. The learning rate is the factor with which new coefficients are weighted in a weighted mean with the old @@ -137,7 +153,8 @@ time stamp and its label. Following plots are generated chronologically: - ExtractStatistic: Extracted information from one or multiple measurements. - Model\_AM: Fitted function for the amplitudes of the power amplifier against the TX amplitude. - Model\_PM: Fitted function for the phase difference of the power amplifier against the TX amplitude. - - adapt.pkl: Contains the settings for the predistortion. To load them again without further measurements, you can use `apply_adapt_dumps.py`. + - adapt.pkl: Contains all settings for the predistortion. + You can load them again without doing measurements with the `apply_adapt_dumps.py` script. - MER: Constellation diagram used to calculate the modulation error rate. After the run you should be able to observe that the peak-to-shoulder @@ -198,7 +215,7 @@ TODO same. - At the moment we assume that the USRP RX gain has to be larger than 30dB and the received signal should have a median absolute value of 0.05 in order to - have a hight quality quantization. Do measurements to support or improve + have a high quality quantization. Do measurements to support or improve this heuristic. - Check if we need to measure MER differently (average over more symbols?) - Is -45dBm the best RX feedback power level? diff --git a/dpd/apply_adapt_dumps.py b/dpd/apply_adapt_dumps.py index 6b15aff..20bc013 100755 --- a/dpd/apply_adapt_dumps.py +++ b/dpd/apply_adapt_dumps.py @@ -6,173 +6,50 @@ # http://www.opendigitalradio.org # Licence: The MIT License, see notice at the end of this file -"""This Python script is the main file for ODR-DabMod's DPD Computation Engine. -This engine calculates and updates the parameter of the digital -predistortion module of ODR-DabMod.""" - import datetime import os -import matplotlib import glob -import natsort -matplotlib.use('GTKAgg') - import logging dt = datetime.datetime.now().isoformat() -logging_path = "/tmp/dpd_{}".format(dt).replace(".", "_").replace(":", "-") -os.makedirs(logging_path) logging.basicConfig(format='%(asctime)s - %(module)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S', - filename='{}/dpd.log'.format(logging_path), - filemode='w', level=logging.DEBUG) -# also log up to INFO to console -console = logging.StreamHandler() -console.setLevel(logging.INFO) -# set a format which is simpler for console use -formatter = logging.Formatter('%(asctime)s - %(module)s - %(levelname)s - %(message)s') -# tell the handler to use this format -console.setFormatter(formatter) -# add the handler to the root logger -logging.getLogger('').addHandler(console) - -import src.Measure as Measure -import src.Model as Model -import src.ExtractStatistic as ExtractStatistic import src.Adapt as Adapt -import src.RX_Agc as Agc -import src.TX_Agc as TX_Agc import argparse -import src.Const -import src.Symbol_align -import src.Measure_Shoulders -import src.MER - parser = argparse.ArgumentParser( - description="DPD Computation Engine for ODR-DabMod") + description="Load pkl dumps DPD settings into ODR-DabMod") parser.add_argument('--port', default=50055, type=int, help='port of DPD server to connect to (default: 50055)', required=False) parser.add_argument('--rc-port', default=9400, type=int, help='port of ODR-DabMod ZMQ Remote Control to connect to (default: 9400)', required=False) -parser.add_argument('--samplerate', default=8192000, type=int, - help='Sample rate', - required=False) -parser.add_argument('--coefs', default='/tmp/poly.coef', +parser.add_argument('--coefs', default='poly.coef', help='File with DPD coefficients, which will be read by ODR-DabMod', required=False) -parser.add_argument('--txgain', default=75, - help='TX Gain', - required=False, - type=int) -parser.add_argument('--rxgain', default=30, - help='TX Gain', - required=False, - type=int) -parser.add_argument('--digital_gain', default=1, - help='Digital Gain', - required=False, - type=float) -parser.add_argument('--samps', default='81920', type=int, - help='Number of samples to request from ODR-DabMod', - required=False) -parser.add_argument('--target_median', default=0.1, - help='target_median', - required=False, - type=float) -parser.add_argument('--searchpath', default='./stored', type=str, - help='Path to search .pkl files with stored configuration' - 'for adapt', - required=False) -parser.add_argument('-L', '--lut', - help='Use lookup table instead of polynomial predistorter', - action="store_true") +parser.add_argument('file', help='File to read the DPD settings from') cli_args = parser.parse_args() port = cli_args.port port_rc = cli_args.rc_port coef_path = cli_args.coefs -digital_gain = cli_args.digital_gain -txgain = cli_args.txgain -rxgain = cli_args.rxgain -num_req = cli_args.samps -samplerate = cli_args.samplerate -searchpath = cli_args.searchpath -target_median = cli_args.target_median - -c = src.Const.Const(samplerate, target_median, False) -SA = src.Symbol_align.Symbol_align(c) -MER = src.MER.MER(c) -MS = src.Measure_Shoulders.Measure_Shoulders(c) - -meas = Measure.Measure(samplerate, port, num_req) -extStat = ExtractStatistic.ExtractStatistic(c) -adapt = Adapt.Adapt(port_rc, coef_path) -dpddata = adapt.get_predistorter - -if cli_args.lut: - model = Model.Lut(c) -else: - model = Model.Poly(c) -adapt.set_predistorter(model.get_dpd_data()) -adapt.set_digital_gain(digital_gain) -adapt.set_txgain(txgain) -adapt.set_rxgain(rxgain) - -tx_gain = adapt.get_txgain() -rx_gain = adapt.get_rxgain() -digital_gain = adapt.get_digital_gain() - -dpddata = adapt.get_predistorter() -if dpddata[0] == "poly": - coefs_am = dpddata[1] - coefs_pm = dpddata[2] - logging.info( - "TX gain {}, RX gain {}, dpd_coefs_am {}," - " dpd_coefs_pm {}, digital_gain {}".format( - tx_gain, rx_gain, coefs_am, coefs_pm, digital_gain - ) - ) -elif dpddata[0] == "lut": - scalefactor = dpddata[1] - lut = dpddata[2] - logging.info( - "TX gain {}, RX gain {}, LUT scalefactor {}," - " LUT {}, digital_gain {}".format( - tx_gain, rx_gain, scalefactor, lut, digital_gain - ) - ) -else: - logging.error("Unknown dpd data format {}".format(dpddata[0])) - -tx_agc = TX_Agc.TX_Agc(adapt, c) - -# Automatic Gain Control -agc = Agc.Agc(meas, adapt, c) -agc.run() - -paths = natsort.natsorted(glob.glob(searchpath + "/*.pkl")) -print(paths) +filename = cli_args.file -for i, path in enumerate(paths): - print(i, path) - adapt.load(path) - dpddata_after = adapt.get_predistorter() +# No need to initialise a GlobalConfig since adapt only needs this one field +class DummyConfig: + def __init__(self): + self.plot_location = None - coefs_am, coefs_pm = model.reset_coefs() - adapt.set_predistorter(("poly", coefs_am, coefs_pm)) - print("Loaded configuration without pre-distortion") +c = DummyConfig() - raw_input("Key for pre-distortion ") - adapt.set_predistorter(dpddata_after) - print("Pre-distortion done") +adapt = Adapt.Adapt(c, port_rc, coef_path) - raw_input("Key for next ") +print("Loading and applying DPD settings from {}".format(filename)) +adapt.load(filename) # The MIT License (MIT) # diff --git a/dpd/dpd.ini b/dpd/dpd.ini index 7e4bd5f..af5c2fb 100644 --- a/dpd/dpd.ini +++ b/dpd/dpd.ini @@ -18,7 +18,7 @@ gainmode=var rate=8192000 # keep in mind that the DPDCE will set the digital gain through the RC! -digital_gain=0.6 +digital_gain=0.01 [firfilter] enabled=1 diff --git a/dpd/environment.yml b/dpd/environment.yml deleted file mode 100644 index 8abedb2..0000000 --- a/dpd/environment.yml +++ /dev/null @@ -1,134 +0,0 @@ -name: dab -channels: -- jochym -- defaults -dependencies: -- _nb_ext_conf=0.4.0=py27_1 -- anaconda-client=1.6.3=py27_0 -- backports=1.0=py27_0 -- backports_abc=0.5=py27_0 -- bleach=1.5.0=py27_0 -- cairo=1.12.18=6 -- clyent=1.2.2=py27_0 -- configparser=3.5.0=py27_0 -- cycler=0.10.0=py27_0 -- dbus=1.10.10=0 -- decorator=4.0.11=py27_0 -- entrypoints=0.2.3=py27_0 -- enum34=1.1.6=py27_0 -- expat=2.1.0=0 -- fontconfig=2.11.1=6 -- freetype=2.5.5=2 -- functools32=3.2.3.2=py27_0 -- get_terminal_size=1.0.0=py27_0 -- glib=2.50.2=1 -- gst-plugins-base=1.8.0=0 -- gstreamer=1.8.0=0 -- harfbuzz=0.9.39=1 -- html5lib=0.9999999=py27_0 -- icu=54.1=0 -- ipykernel=4.6.1=py27_0 -- ipython=5.3.0=py27_0 -- ipython_genutils=0.2.0=py27_0 -- ipywidgets=6.0.0=py27_0 -- jinja2=2.9.6=py27_0 -- jpeg=8d=2 -- jsonschema=2.6.0=py27_0 -- jupyter=1.0.0=py27_3 -- jupyter_client=5.1.0=py27_0 -- jupyter_console=5.1.0=py27_0 -- jupyter_core=4.3.0=py27_0 -- libffi=3.2.1=1 -- libgcc=5.2.0=0 -- libgfortran=3.0.0=1 -- libiconv=1.14=0 -- libpng=1.6.27=0 -- libsodium=1.0.10=0 -- libxcb=1.12=1 -- libxml2=2.9.4=0 -- markupsafe=0.23=py27_2 -- matplotlib=2.0.2=np112py27_0 -- mistune=0.7.4=py27_0 -- mkl=2017.0.1=0 -- natsort=5.0.3=py27_0 -- nb_anacondacloud=1.4.0=py27_0 -- nb_conda=2.2.0=py27_0 -- nb_conda_kernels=2.1.0=py27_0 -- nbconvert=5.2.1=py27_0 -- nbformat=4.3.0=py27_0 -- nbpresent=3.0.2=py27_0 -- notebook=5.0.0=py27_0 -- numpy=1.12.1=py27_0 -- openssl=1.0.2k=2 -- pandas=0.20.1=np112py27_0 -- pandocfilters=1.4.1=py27_0 -- pango=1.39.0=0 -- path.py=10.3.1=py27_0 -- pathlib2=2.3.0=py27_0 -- pcre=8.39=1 -- pexpect=4.2.1=py27_0 -- pickleshare=0.7.4=py27_0 -- pip=9.0.1=py27_1 -- pixman=0.32.6=0 -- plotly=2.0.9=py27_0 -- prompt_toolkit=1.0.14=py27_0 -- ptyprocess=0.5.2=py27_0 -- py2cairo=1.10.0=py27_2 -- pycairo=1.10.0=py27_0 -- pygments=2.2.0=py27_0 -- pyparsing=2.1.4=py27_0 -- pyqt=5.6.0=py27_2 -- python=2.7.13=0 -- python-dateutil=2.6.0=py27_0 -- pytz=2017.2=py27_0 -- pyyaml=3.12=py27_0 -- pyzmq=16.0.2=py27_0 -- qt=5.6.2=0 -- qtconsole=4.3.0=py27_0 -- readline=6.2=2 -- requests=2.14.2=py27_0 -- scandir=1.5=py27_0 -- scikit-learn=0.18.1=np112py27_1 -- scipy=0.19.0=np112py27_0 -- setuptools=27.2.0=py27_0 -- simplegeneric=0.8.1=py27_1 -- singledispatch=3.4.0.3=py27_0 -- sip=4.18=py27_0 -- six=1.10.0=py27_0 -- sqlite=3.13.0=0 -- ssl_match_hostname=3.4.0.2=py27_1 -- subprocess32=3.2.7=py27_0 -- terminado=0.6=py27_0 -- testpath=0.3.1=py27_0 -- tk=8.5.18=0 -- tornado=4.5.1=py27_0 -- tqdm=4.11.2=py27_0 -- traitlets=4.3.2=py27_0 -- wcwidth=0.1.7=py27_0 -- wheel=0.29.0=py27_0 -- widgetsnbextension=2.0.0=py27_0 -- yaml=0.1.6=0 -- zeromq=4.1.5=0 -- zlib=1.2.8=3 -- atk=2.8.0=0 -- gdk-pixbuf=2.28.2=0 -- gtk2=2.24.28=0 -- pygobject2=2.28.6=0 -- pygtk2=2.24.0=0 -- pip: - - backports-abc==0.5 - - backports.shutil-get-terminal-size==1.0.0 - - backports.ssl-match-hostname==3.4.0.2 - - ipython-genutils==0.2.0 - - jupyter-client==5.1.0 - - jupyter-console==5.1.0 - - jupyter-core==4.3.0 - - multiprocessing==2.6.2.1 - - nb-anacondacloud==1.4.0 - - nb-conda==2.2.0 - - nb-conda-kernels==2.1.0 - - prompt-toolkit==1.0.14 - - pynverse==0.1.4.4 - - zmq==0.0.0 -prefix: /home/andreas/miniconda2/envs/dab - diff --git a/dpd/main.py b/dpd/main.py index 49c0d1d..093f227 100755 --- a/dpd/main.py +++ b/dpd/main.py @@ -11,6 +11,7 @@ This engine calculates and updates the parameter of the digital predistortion module of ODR-DabMod.""" +import sys import datetime import os import argparse @@ -45,7 +46,7 @@ parser.add_argument('--digital_gain', default=0.6, required=False, type=float) parser.add_argument('--target_median', default=0.05, - help='target_median', + help='The target median for the RX and TX AGC', required=False, type=float) parser.add_argument('--samps', default='81920', type=int, @@ -58,10 +59,14 @@ parser.add_argument('-L', '--lut', help='Use lookup table instead of polynomial predistorter', action="store_true") parser.add_argument('--plot', - help='Enable all plots, to be more selective choose plots in Const.py', + help='Enable all plots, to be more selective choose plots in GlobalConfig.py', action="store_true") parser.add_argument('--name', default="", type=str, help='Name of the logging directory') +parser.add_argument('-r', '--reset', action="store_true", + help='Reset the DPD settings to the defaults.') +parser.add_argument('-s', '--status', action="store_true", + help='Display the currently running DPD settings.') cli_args = parser.parse_args() @@ -81,27 +86,36 @@ plot = cli_args.plot # Logging import logging -dt = datetime.datetime.now().isoformat() -logging_path = '/tmp/dpd_{}'.format(dt).replace('.', '_').replace(':', '-') -if name: - logging_path += '_' + name -os.makedirs(logging_path) -logging.basicConfig(format='%(asctime)s - %(module)s - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S', - filename='{}/dpd.log'.format(logging_path), - filemode='w', - level=logging.DEBUG) -# also log up to INFO to console -console = logging.StreamHandler() -console.setLevel(logging.INFO) -# set a format which is simpler for console use -formatter = logging.Formatter('%(asctime)s - %(module)s - %(levelname)s - %(message)s') -# tell the handler to use this format -console.setFormatter(formatter) -# add the handler to the root logger -logging.getLogger('').addHandler(console) - -logging.info(cli_args) +# Simple usage scenarios don't need to clutter /tmp +if not (cli_args.status or cli_args.reset): + dt = datetime.datetime.now().isoformat() + logging_path = '/tmp/dpd_{}'.format(dt).replace('.', '_').replace(':', '-') + if name: + logging_path += '_' + name + print("Logs and plots written to {}".format(logging_path)) + os.makedirs(logging_path) + logging.basicConfig(format='%(asctime)s - %(module)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + filename='{}/dpd.log'.format(logging_path), + filemode='w', + level=logging.DEBUG) + # also log up to INFO to console + console = logging.StreamHandler() + console.setLevel(logging.INFO) + # set a format which is simpler for console use + formatter = logging.Formatter('%(asctime)s - %(module)s - %(levelname)s - %(message)s') + # tell the handler to use this format + console.setFormatter(formatter) + # add the handler to the root logger + logging.getLogger('').addHandler(console) +else: + dt = datetime.datetime.now().isoformat() + logging.basicConfig(format='%(asctime)s - %(module)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + level=logging.DEBUG) + logging_path = None + +logging.info("DPDCE starting up with options: {}".format(cli_args)) import numpy as np import traceback @@ -109,26 +123,38 @@ from src.Model import Lut, Poly import src.Heuristics as Heuristics from src.Measure import Measure from src.ExtractStatistic import ExtractStatistic -from src.Adapt import Adapt +from src.Adapt import Adapt, dpddata_to_str from src.RX_Agc import Agc from src.TX_Agc import TX_Agc from src.Symbol_align import Symbol_align -from src.Const import Const +from src.GlobalConfig import GlobalConfig from src.MER import MER from src.Measure_Shoulders import Measure_Shoulders -c = Const(samplerate, target_median, plot) +c = GlobalConfig(cli_args, logging_path) SA = Symbol_align(c) MER = MER(c) MS = Measure_Shoulders(c) -meas = Measure(samplerate, port, num_req) +meas = Measure(c, samplerate, port, num_req) extStat = ExtractStatistic(c) -adapt = Adapt(port_rc, coef_path) +adapt = Adapt(c, port_rc, coef_path) + +if cli_args.status: + txgain = adapt.get_txgain() + rxgain = adapt.get_rxgain() + digital_gain = adapt.get_digital_gain() + dpddata = dpddata_to_str(adapt.get_predistorter()) + + logging.info("ODR-DabMod currently running with TXGain {}, RXGain {}, digital gain {} and {}".format( + rxgain, rxgain, digital_gain, dpddata)) + sys.exit(0) if cli_args.lut: model = Lut(c) else: model = Poly(c) + +# Models have the default settings on startup adapt.set_predistorter(model.get_dpd_data()) adapt.set_digital_gain(digital_gain) @@ -149,26 +175,13 @@ rx_gain = adapt.get_rxgain() digital_gain = adapt.get_digital_gain() dpddata = adapt.get_predistorter() -if dpddata[0] == "poly": - coefs_am = dpddata[1] - coefs_pm = dpddata[2] - logging.info( - "TX gain {}, RX gain {}, dpd_coefs_am {}," - " dpd_coefs_pm {}, digital_gain {}".format( - tx_gain, rx_gain, coefs_am, coefs_pm, digital_gain - ) - ) -elif dpddata[0] == "lut": - scalefactor = dpddata[1] - lut = dpddata[2] - logging.info( - "TX gain {}, RX gain {}, LUT scalefactor {}," - " LUT {}, digital_gain {}".format( - tx_gain, rx_gain, scalefactor, lut, digital_gain - ) - ) -else: - logging.error("Unknown dpd data format {}".format(dpddata[0])) + +logging.info("TX gain {}, RX gain {}, digital_gain {}, {!s}".format( + tx_gain, rx_gain, digital_gain, dpddata_to_str(dpddata))) + +if cli_args.reset: + logging.info("DPD Settings were reset to default values.") + sys.exit(0) tx_agc = TX_Agc(adapt, c) diff --git a/dpd/src/Adapt.py b/dpd/src/Adapt.py index 7e19a2c..329ee20 100644 --- a/dpd/src/Adapt.py +++ b/dpd/src/Adapt.py @@ -16,8 +16,6 @@ import os import datetime import pickle -logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) - LUT_LEN = 32 FORMAT_POLY = 1 FORMAT_LUT = 2 @@ -44,6 +42,19 @@ def _write_lut_file(scalefactor, lut, path): f.write("{}\n{}\n".format(coef.real, coef.imag)) f.close() +def dpddata_to_str(dpddata): + if dpddata[0] == "poly": + coefs_am = dpddata[1] + coefs_pm = dpddata[2] + return "dpd_coefs_am {}, dpd_coefs_pm {}".format( + coefs_am, coefs_pm) + elif dpddata[0] == "lut": + scalefactor = dpddata[1] + lut = dpddata[2] + return "LUT scalefactor {}, LUT {}".format( + scalefactor, lut) + else: + raise ValueError("Unknown dpddata type {}".format(dpddata[0])) class Adapt: """Uses the ZMQ remote control to change parameters of the DabMod @@ -55,8 +66,9 @@ class Adapt: ZMQ remote control. """ - def __init__(self, port, coef_path): + def __init__(self, config, port, coef_path): logging.debug("Instantiate Adapt object") + self.c = config self.port = port self.coef_path = coef_path self.host = "localhost" @@ -226,27 +238,30 @@ class Adapt: """Backup current settings to a file""" dt = datetime.datetime.now().isoformat() if path is None: - path = logging_path + "/" + dt + "_adapt.pkl" + if self.c.plot_location is not None: + path = self.c.plot_location + "/" + dt + "_adapt.pkl" + else: + raise Exception("Cannot dump Adapt without either plot_location or path set") d = { "txgain": self.get_txgain(), "rxgain": self.get_rxgain(), "digital_gain": self.get_digital_gain(), "predistorter": self.get_predistorter() } - with open(path, "w") as f: + with open(path, "wb") as f: pickle.dump(d, f) return path def load(self, path): """Restore settings from a file""" - with open(path, "r") as f: + with open(path, "rb") as f: d = pickle.load(f) - self.set_txgain(d["txgain"]) - self.set_digital_gain(d["digital_gain"]) - self.set_rxgain(d["rxgain"]) - self.set_predistorter(d["predistorter"]) + self.set_txgain(d["txgain"]) + self.set_digital_gain(d["digital_gain"]) + self.set_rxgain(d["rxgain"]) + self.set_predistorter(d["predistorter"]) # The MIT License (MIT) # diff --git a/dpd/src/Dab_Util.py b/dpd/src/Dab_Util.py index 2021f38..bc89a39 100644 --- a/dpd/src/Dab_Util.py +++ b/dpd/src/Dab_Util.py @@ -8,9 +8,6 @@ import datetime import os import logging - -logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) - import numpy as np import matplotlib @@ -33,10 +30,11 @@ class Dab_Util: complex IQ samples of a DAB signal """ - def __init__(self, sample_rate, plot=False): + def __init__(self, config, sample_rate, plot=False): """ :param sample_rate: sample rate [sample/sec] to use for calculations """ + self.c = config self.sample_rate = sample_rate self.dab_bandwidth = 1536000 # Bandwidth of a dab signal self.frame_ms = 96 # Duration of a Dab frame @@ -53,9 +51,9 @@ class Dab_Util: off = sig_rec.shape[0] c = np.abs(signal.correlate(sig_orig, sig_rec)) - if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot: + if self.plot and self.c.plot_location is not None: dt = datetime.datetime.now().isoformat() - corr_path = (logging_path + "/" + dt + "_tx_rx_corr.svg") + corr_path = self.c.plot_location + "/" + dt + "_tx_rx_corr.png" plt.plot(c, label="corr") plt.legend() plt.savefig(corr_path) @@ -107,9 +105,9 @@ class Dab_Util: Returns an aligned version of sig_tx and sig_rx by cropping and subsample alignment """ - if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot: + if self.plot and self.c.plot_location is not None: dt = datetime.datetime.now().isoformat() - fig_path = logging_path + "/" + dt + "_sync_raw.svg" + fig_path = self.c.plot_location + "/" + dt + "_sync_raw.png" fig, axs = plt.subplots(2) axs[0].plot(np.abs(sig_tx[:128]), label="TX Frame") @@ -151,9 +149,9 @@ class Dab_Util: sig_tx = sig_tx[:-1] sig_rx = sig_rx[:-1] - if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot: + if self.plot and self.c.plot_location is not None: dt = datetime.datetime.now().isoformat() - fig_path = logging_path + "/" + dt + "_sync_sample_aligned.svg" + fig_path = self.c.plot_location + "/" + dt + "_sync_sample_aligned.png" fig, axs = plt.subplots(2) axs[0].plot(np.abs(sig_tx[:128]), label="TX Frame") @@ -175,9 +173,9 @@ class Dab_Util: sig_rx = sa.subsample_align(sig_rx, sig_tx) - if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot: + if self.plot and self.c.plot_location is not None: dt = datetime.datetime.now().isoformat() - fig_path = logging_path + "/" + dt + "_sync_subsample_aligned.svg" + fig_path = self.c.plot_location + "/" + dt + "_sync_subsample_aligned.png" fig, axs = plt.subplots(2) axs[0].plot(np.abs(sig_tx[:128]), label="TX Frame") @@ -199,9 +197,9 @@ class Dab_Util: sig_rx = pa.phase_align(sig_rx, sig_tx) - if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot: + if self.plot and self.c.plot_location is not None: dt = datetime.datetime.now().isoformat() - fig_path = logging_path + "/" + dt + "_sync_phase_aligned.svg" + fig_path = self.c.plot_location + "/" + dt + "_sync_phase_aligned.png" fig, axs = plt.subplots(2) axs[0].plot(np.abs(sig_tx[:128]), label="TX Frame") diff --git a/dpd/src/ExtractStatistic.py b/dpd/src/ExtractStatistic.py index d27cd77..639513a 100644 --- a/dpd/src/ExtractStatistic.py +++ b/dpd/src/ExtractStatistic.py @@ -8,13 +8,10 @@ import numpy as np import matplotlib.pyplot as plt - import datetime import os import logging -logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) - def _check_input_extract(tx_dpd, rx_received): # Check data type @@ -64,10 +61,9 @@ class ExtractStatistic: self.plot = c.ES_plot def _plot_and_log(self, tx_values, rx_values, phase_diffs_values, phase_diffs_values_lists): - if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot: - + if self.plot and self.c.plot_location is not None: dt = datetime.datetime.now().isoformat() - fig_path = logging_path + "/" + dt + "_ExtractStatistic.png" + fig_path = self.c.plot_location + "/" + dt + "_ExtractStatistic.png" sub_rows = 3 sub_cols = 1 fig = plt.figure(figsize=(sub_cols * 6, sub_rows / 2. * 6)) diff --git a/dpd/src/Const.py b/dpd/src/GlobalConfig.py index 6c9bafa..b84b9d7 100644 --- a/dpd/src/Const.py +++ b/dpd/src/GlobalConfig.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# DPD Computation Engine, constants. +# DPD Computation Engine, constants and global configuration # # Source for DAB standard: etsi_EN_300_401_v010401p p145 # @@ -9,19 +9,20 @@ import numpy as np +class GlobalConfig: + def __init__(self, cli_args, plot_location): + self.sample_rate = cli_args.samplerate + assert self.sample_rate == 8192000 # By now only constants for 8192000 -class Const: - def __init__(self, sample_rate, target_median, plot): - assert sample_rate == 8192000 # By now only constants for 8192000 - self.sample_rate = sample_rate + self.plot_location = plot_location # DAB frame # Time domain - self.T_F = sample_rate / 2048000 * 196608 # Transmission frame duration - self.T_NULL = sample_rate / 2048000 * 2656 # Null symbol duration - self.T_S = sample_rate / 2048000 * 2552 # Duration of OFDM symbols of indices l = 1, 2, 3,... L; - self.T_U = sample_rate / 2048000 * 2048 # Inverse of carrier spacing - self.T_C = sample_rate / 2048000 * 504 # Duration of cyclic prefix + self.T_F = self.sample_rate / 2048000 * 196608 # Transmission frame duration + self.T_NULL = self.sample_rate / 2048000 * 2656 # Null symbol duration + self.T_S = self.sample_rate / 2048000 * 2552 # Duration of OFDM symbols of indices l = 1, 2, 3,... L; + self.T_U = self.sample_rate / 2048000 * 2048 # Inverse of carrier spacing + self.T_C = self.sample_rate / 2048000 * 504 # Duration of cyclic prefix # Frequency Domain # example: np.delete(fft[3328:4865], 768) @@ -34,10 +35,10 @@ class Const: # time per sample = 1 / sample_rate # frequency per bin = 1kHz # phase difference per sample offset = delta_t * 2 * pi * delta_freq - self.phase_offset_per_sample = 1. / sample_rate * 2 * np.pi * 1000 + self.phase_offset_per_sample = 1. / self.sample_rate * 2 * np.pi * 1000 # Constants for ExtractStatistic - self.ES_plot = plot + self.ES_plot = cli_args.plot self.ES_start = 0.0 self.ES_end = 1.0 self.ES_n_bins = 64 # Number of bins between ES_start and ES_end @@ -45,7 +46,7 @@ class Const: # Constants for Measure_Shoulder self.MS_enable = False - self.MS_plot = plot + self.MS_plot = cli_args.plot meas_offset = 976 # Offset from center frequency to measure shoulder [kHz] meas_width = 100 # Size of frequency delta to measure shoulder [kHz] @@ -63,24 +64,24 @@ class Const: self.MS_n_proc = 4 # Constants for MER - self.MER_plot = plot + self.MER_plot = cli_args.plot # Constants for Model - self.MDL_plot = True or plot # Override default + self.MDL_plot = cli_args.plot # Constants for Model_PM # Set all phase offsets to zero for TX amplitude < MPM_tx_min self.MPM_tx_min = 0.1 # Constants for TX_Agc - self.TAGC_max_txgain = 89 # USRP specific - self.TAGC_tx_median_target = target_median + self.TAGC_max_txgain = 89 # USRP B200 specific + self.TAGC_tx_median_target = cli_args.target_median self.TAGC_tx_median_max = self.TAGC_tx_median_target * 1.4 self.TAGC_tx_median_min = self.TAGC_tx_median_target / 1.4 # Constants for RX_AGC - self.RAGC_min_rxgain = 25 # USRP specific - self.RAGC_rx_median_target = self.TAGC_tx_median_target + self.RAGC_min_rxgain = 25 # USRP B200 specific + self.RAGC_rx_median_target = cli_args.target_median # The MIT License (MIT) # diff --git a/dpd/src/MER.py b/dpd/src/MER.py index f186261..693058d 100644 --- a/dpd/src/MER.py +++ b/dpd/src/MER.py @@ -8,11 +8,6 @@ import datetime import os import logging -try: - logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) -except: - logging_path = "/tmp/" - import numpy as np import matplotlib matplotlib.use('agg') @@ -76,9 +71,11 @@ class MER: spectrum = self._calc_spectrum(tx) - if self.plot: + if self.plot and self.c.plot_location is not None: dt = datetime.datetime.now().isoformat() - fig_path = logging_path + "/" + dt + "_MER" + debug_name + ".svg" + fig_path = self.c.plot_location + "/" + dt + "_MER" + debug_name + ".png" + else: + fig_path = None MERs = [] for i, (x, y) in enumerate(self._split_in_carrier( @@ -103,7 +100,7 @@ class MER: ylim = ax.get_ylim() ax.set_ylim(ylim[0] - (ylim[1] - ylim[0]) * 0.1, ylim[1]) - if self.plot: + if fig_path is not None: plt.tight_layout() plt.savefig(fig_path) plt.show() diff --git a/dpd/src/Measure.py b/dpd/src/Measure.py index d4b1d9e..b7423c6 100644 --- a/dpd/src/Measure.py +++ b/dpd/src/Measure.py @@ -11,14 +11,13 @@ import struct import numpy as np import src.Dab_Util as DU import os - import logging -logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) class Measure: """Collect Measurement from DabMod""" - def __init__(self, samplerate, port, num_samples_to_request): + def __init__(self, config, samplerate, port, num_samples_to_request): logging.info("Instantiate Measure object") + self.c = config self.samplerate = samplerate self.sizeof_sample = 8 # complex floats self.port = port @@ -106,7 +105,7 @@ class Measure: rx_median = np.median(np.abs(rxframe)) rxframe = rxframe / rx_median * np.median(np.abs(txframe)) - du = DU.Dab_Util(self.samplerate) + du = DU.Dab_Util(self.c, self.samplerate) txframe_aligned, rxframe_aligned = du.subsample_align(txframe, rxframe) logging.info( diff --git a/dpd/src/Measure_Shoulders.py b/dpd/src/Measure_Shoulders.py index c733dfd..fd90050 100644 --- a/dpd/src/Measure_Shoulders.py +++ b/dpd/src/Measure_Shoulders.py @@ -9,9 +9,6 @@ import datetime import os import logging import multiprocessing - -logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) - import numpy as np import matplotlib.pyplot as plt @@ -79,8 +76,11 @@ class Measure_Shoulders: self.plot = c.MS_plot def _plot(self, signal): + if self.c.plot_location is None: + return + dt = datetime.datetime.now().isoformat() - fig_path = logging_path + "/" + dt + "_sync_subsample_aligned.svg" + fig_path = self.c.plot_location + "/" + dt + "_sync_subsample_aligned.png" fft = calc_fft_db(signal, 100, self.c) peak, idxs_peak = _calc_peak(fft, self.c) diff --git a/dpd/src/Model.py b/dpd/src/Model.py index 67feeb6..b2c303f 100644 --- a/dpd/src/Model.py +++ b/dpd/src/Model.py @@ -2,6 +2,12 @@ from src.Model_Poly import Poly from src.Model_Lut import Lut +def select_model_from_dpddata(dpddata): + if dpddata[0] == 'lut': + return Lut + elif dpddata[0] == 'poly': + return Poly + # The MIT License (MIT) # # Copyright (c) 2017 Andreas Steger diff --git a/dpd/src/Model_AM.py b/dpd/src/Model_AM.py index d7e880c..9800d83 100644 --- a/dpd/src/Model_AM.py +++ b/dpd/src/Model_AM.py @@ -8,9 +8,6 @@ import datetime import os import logging - -logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) - import numpy as np import matplotlib.pyplot as plt @@ -55,12 +52,12 @@ class Model_AM: self.plot = plot def _plot(self, tx_dpd, rx_received, coefs_am, coefs_am_new): - if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot: + if self.plot and self.c.plot_location is not None: tx_range, rx_est = calc_line(coefs_am, 0, 0.6) tx_range_new, rx_est_new = calc_line(coefs_am_new, 0, 0.6) dt = datetime.datetime.now().isoformat() - fig_path = logging_path + "/" + dt + "_Model_AM.svg" + fig_path = self.c.plot_location + "/" + dt + "_Model_AM.png" sub_rows = 1 sub_cols = 1 fig = plt.figure(figsize=(sub_cols * 6, sub_rows / 2. * 6)) diff --git a/dpd/src/Model_Lut.py b/dpd/src/Model_Lut.py index 6d4db52..e70fdb0 100644 --- a/dpd/src/Model_Lut.py +++ b/dpd/src/Model_Lut.py @@ -7,12 +7,8 @@ import os import logging - -logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) - import numpy as np - class Lut: """Implements a model that calculates lookup table coefficients""" diff --git a/dpd/src/Model_PM.py b/dpd/src/Model_PM.py index d4f8c00..3aafea0 100644 --- a/dpd/src/Model_PM.py +++ b/dpd/src/Model_PM.py @@ -8,9 +8,6 @@ import datetime import os import logging - -logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) - import numpy as np import matplotlib.pyplot as plt @@ -41,12 +38,12 @@ class Model_PM: self.plot = plot def _plot(self, tx_dpd, phase_diff, coefs_pm, coefs_pm_new): - if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot: + if self.plot and self.c.plot_location is not None: tx_range, phase_diff_est = self.calc_line(coefs_pm, 0, 0.6) tx_range_new, phase_diff_est_new = self.calc_line(coefs_pm_new, 0, 0.6) dt = datetime.datetime.now().isoformat() - fig_path = logging_path + "/" + dt + "_Model_PM.svg" + fig_path = self.c.plot_location + "/" + dt + "_Model_PM.png" sub_rows = 1 sub_cols = 1 fig = plt.figure(figsize=(sub_cols * 6, sub_rows / 2. * 6)) diff --git a/dpd/src/Model_Poly.py b/dpd/src/Model_Poly.py index ff15941..cdfd319 100644 --- a/dpd/src/Model_Poly.py +++ b/dpd/src/Model_Poly.py @@ -7,9 +7,6 @@ import os import logging - -logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) - import numpy as np import src.Model_AM as Model_AM @@ -54,13 +51,10 @@ class Poly: self.model_am = Model_AM.Model_AM(c, plot=self.plot) self.model_pm = Model_PM.Model_PM(c, plot=self.plot) - self.plot = c.MDL_plot - def reset_coefs(self): self.coefs_am = np.zeros(5, dtype=np.float32) self.coefs_am[0] = 1 self.coefs_pm = np.zeros(5, dtype=np.float32) - return self.coefs_am, self.coefs_pm def train(self, tx_abs, rx_abs, phase_diff, lr=None): """ diff --git a/dpd/src/RX_Agc.py b/dpd/src/RX_Agc.py index 670fbbb..f778dee 100644 --- a/dpd/src/RX_Agc.py +++ b/dpd/src/RX_Agc.py @@ -9,8 +9,6 @@ import datetime import os import logging import time -logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) - import numpy as np import matplotlib matplotlib.use('agg') @@ -70,11 +68,14 @@ class Agc: def plot_estimates(self): """Plots the estimate of for Max, Median, Mean for different number of samples.""" + if self.c.plot_location is None: + return + self.adapt.set_rxgain(self.min_rxgain) time.sleep(1) dt = datetime.datetime.now().isoformat() - fig_path = logging_path + "/" + dt + "_agc.svg" + fig_path = self.c.plot_location + "/" + dt + "_agc.png" fig, axs = plt.subplots(2, 2, figsize=(3*6,1*6)) axs = axs.ravel() diff --git a/dpd/src/Symbol_align.py b/dpd/src/Symbol_align.py index d921f25..2a17a65 100644 --- a/dpd/src/Symbol_align.py +++ b/dpd/src/Symbol_align.py @@ -8,12 +8,6 @@ import datetime import os import logging - -try: - logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) -except: - logging_path = "/tmp/" - import numpy as np import scipy import matplotlib @@ -75,9 +69,9 @@ class Symbol_align: offset = peaks[np.argmin([tx_product_avg[peak] for peak in peaks])] - if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot: + if self.plot and self.c.plot_location is not None: dt = datetime.datetime.now().isoformat() - fig_path = logging_path + "/" + dt + "_Symbol_align.svg" + fig_path = self.c.plot_location + "/" + dt + "_Symbol_align.png" fig = plt.figure(figsize=(9, 9)) diff --git a/dpd/src/TX_Agc.py b/dpd/src/TX_Agc.py index 3c804fa..309193d 100644 --- a/dpd/src/TX_Agc.py +++ b/dpd/src/TX_Agc.py @@ -9,9 +9,6 @@ import datetime import os import logging import time - -logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) - import numpy as np import matplotlib diff --git a/dpd/src/phase_align.py b/dpd/src/phase_align.py index 68c216d..8654333 100644 --- a/dpd/src/phase_align.py +++ b/dpd/src/phase_align.py @@ -7,8 +7,6 @@ import datetime import os import logging -logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) - import numpy as np import matplotlib.pyplot as plt @@ -24,9 +22,9 @@ def phase_align(sig, ref_sig, plot=False): real_diffs = np.cos(angle_diff) imag_diffs = np.sin(angle_diff) - if logging.getLogger().getEffectiveLevel() == logging.DEBUG and plot: + if plot and self.c.plot_location is not None: dt = datetime.datetime.now().isoformat() - fig_path = logging_path + "/" + dt + "_phase_align.svg" + fig_path = self.c.plot_location + "/" + dt + "_phase_align.png" plt.subplot(511) plt.hist(angle_diff, bins=60, label="Angle Diff") diff --git a/dpd/src/subsample_align.py b/dpd/src/subsample_align.py index 68f3591..20ae56b 100755 --- a/dpd/src/subsample_align.py +++ b/dpd/src/subsample_align.py @@ -7,14 +7,10 @@ import datetime import logging import os - -logging_path = os.path.dirname(logging.getLoggerClass().root.handlers[0].baseFilename) - import numpy as np from scipy import optimize import matplotlib.pyplot as plt - def gen_omega(length): if (length % 2) == 1: raise ValueError("Needs an even length array.") @@ -32,7 +28,7 @@ def gen_omega(length): return omega -def subsample_align(sig, ref_sig, plot=False): +def subsample_align(sig, ref_sig, plot_location=None): """Do subsample alignment for sig relative to the reference signal ref_sig. The delay between the two must be less than sample @@ -72,13 +68,13 @@ def subsample_align(sig, ref_sig, plot=False): if optim_result.success: best_tau = optim_result.x - if plot: + if plot_location is not None: corr = np.vectorize(correlate_for_delay) ixs = np.linspace(-1, 1, 100) taus = corr(ixs) dt = datetime.datetime.now().isoformat() - tau_path = (logging_path + "/" + dt + "_tau.svg") + tau_path = (plot_location + "/" + dt + "_tau.png") plt.plot(ixs, taus) plt.title("Subsample correlation, minimum is best: {}".format(best_tau)) plt.savefig(tau_path) |