From 9551c8adb5c909e2072b4fad6f9ebdc4c93c848a Mon Sep 17 00:00:00 2001 From: andreas128 Date: Mon, 18 Sep 2017 13:16:06 +0200 Subject: Add Measure_Shoulders --- .gitignore | 14 +++++ dpd/main.py | 11 +++- dpd/src/Measure_Shoulders.py | 131 +++++++++++++++++++++++++++++++++++++++++++ dpd/src/const.py | 2 + 4 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 dpd/src/Measure_Shoulders.py diff --git a/.gitignore b/.gitignore index d797fb1..e9eac8e 100644 --- a/.gitignore +++ b/.gitignore @@ -138,3 +138,17 @@ Session.vim tags # End of https://www.gitignore.io/api/vim,python +*.ipynb + +.directory +.idea/ +.ycm_extra_conf.py +coef_83 +doc/.directory +dpd.zip +dpd/.idea/ +dpd/bin/ +dpd/run.sh +dpd/test.csv +dpd/test_data/ +dpd/dpdpoly.coef diff --git a/dpd/main.py b/dpd/main.py index b4355c3..ee75146 100755 --- a/dpd/main.py +++ b/dpd/main.py @@ -49,6 +49,7 @@ import src.TX_Agc as TX_Agc import src.Symbol_align import src.const import src.MER +import src.Measure_Shoulders import argparse parser = argparse.ArgumentParser( @@ -65,7 +66,7 @@ parser.add_argument('--samplerate', default=8192000, type=int, 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=73, +parser.add_argument('--txgain', default=70, help='TX Gain', required=False, type=int) @@ -99,9 +100,10 @@ num_req = cli_args.samps samplerate = cli_args.samplerate num_iter = cli_args.iterations +c = src.const.const(samplerate) SA = src.Symbol_align.Symbol_align(samplerate) MER = src.MER.MER(samplerate) -c = src.const.const(samplerate) +MS = src.Measure_Shoulders.Measure_Shoulder(c, plot=True) meas = Measure.Measure(samplerate, port, num_req) extStat = ExtractStatistic.ExtractStatistic(c, plot=True) @@ -195,9 +197,12 @@ while i < num_iter: rx_gain = adapt.get_rxgain() digital_gain = adapt.get_digital_gain() tx_median = np.median(np.abs(txframe_aligned)) + rx_shoulders = MS.average_shoulders(rxframe_aligned) + tx_shoulders = MS.average_shoulders(txframe_aligned) logging.info(list((name, eval(name)) for name in - ['i', 'tx_mer', 'rx_mer', 'mse', 'tx_gain', + ['i', 'tx_mer', 'tx_shoulders', 'rx_mer', + 'rx_shoulders', 'mse', 'tx_gain', 'digital_gain', 'rx_gain', 'rx_median', 'tx_median'])) if dpddata[0] == "poly": diff --git a/dpd/src/Measure_Shoulders.py b/dpd/src/Measure_Shoulders.py new file mode 100644 index 0000000..d2b4b5b --- /dev/null +++ b/dpd/src/Measure_Shoulders.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# +# DPD Calculation Engine, calculate peak to shoulder difference +# +# http://www.opendigitalradio.org +# Licence: The MIT License, see notice at the end of this file + +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 + +def plt_next_axis(sub_rows, sub_cols, i_sub): + i_sub += 1 + ax = plt.subplot(sub_rows, sub_cols, i_sub) + return i_sub, ax + +def plt_annotate(ax, x,y,title=None,legend_loc=None): + ax.set_xlabel(x) + ax.set_ylabel(y) + if title is not None: ax.set_title(title) + if legend_loc is not None: ax.legend(loc=legend_loc) + + +class Measure_Shoulder: + """Calculate difference between the DAB signal and the shoulder hight in the + power spectrum""" + + def __init__(self, + c, + plot=False): + self.c = c + self.plot = plot + + def calc_fft_db(self, signal, offset=0): + fft = np.fft.fftshift(np.fft.fft(signal[offset:offset + self.c.MS_FFT_size])) + fft_db = 20 * np.log10(np.abs(fft)) + return fft_db + + def _calc_peak(self, fft): + assert fft.shape == (self.c.MS_FFT_size,), fft.shape + idxs = (self.c.MS_peak_start, self.c.MS_peak_end) + peak = np.mean(fft[idxs[0]:idxs[1]]) + return peak, idxs + + def _calc_shoulder_hight(self, fft_db): + assert fft_db.shape == (self.c.MS_FFT_size,), fft_db.shape + idxs_left = (self.c.MS_shoulder_left_start, self.c.MS_shoulder_left_end) + idxs_right = (self.c.MS_shoulder_right_start, self.c.MS_shoulder_right_end) + + shoulder_left = np.mean(fft_db[idxs_left[0]:idxs_left[1]]) + shoulder_right = np.mean(fft_db[idxs_right[0]:idxs_right[1]]) + + shoulder = np.mean((shoulder_left, shoulder_right)) + return shoulder, (idxs_left, idxs_right) + + def calc_shoulder(self, fft): + peak = self._calc_peak(fft)[0] + shoulder = self._calc_shoulder_hight(fft)[0] + assert (peak >= shoulder), (peak, shoulder) + return peak - shoulder + + def _plot(self, signal): + fft = self.calc_fft_db(signal, 100) + peak, idxs_peak = self._calc_peak(fft) + shoulder, idxs_sh = self._calc_shoulder_hight(fft) + + sub_rows = 1 + sub_cols = 1 + fig = plt.figure(figsize=(sub_cols * 6, sub_rows / 2. * 6)) + i_sub = 0 + + i_sub, ax = plt_next_axis(sub_rows, sub_cols, i_sub) + ax.scatter(np.arange(fft.shape[0]), fft, s=0.1, + label="FFT", + color="red") + ax.plot(idxs_peak, (peak, peak)) + ax.plot(idxs_sh[0], (shoulder, shoulder), color='blue') + ax.plot(idxs_sh[1], (shoulder, shoulder), color='blue') + plt_annotate(ax, "Frequency", "Magnitude [dB]", None, 4) + + ax.text(100, -17, str(self.calc_shoulder(fft))) + + ax.set_ylim(-20, 30) + fig.tight_layout() + + def average_shoulders(self, signal, n_avg=None): + assert signal.shape[0] > 4 * self.c.MS_FFT_size + if n_avg is None: n_avg = self.c.MS_averaging_size + + off_min = 0 + off_max = signal.shape[0] - self.c.MS_FFT_size + offsets = np.linspace(off_min, off_max, num=n_avg, dtype=int) + + shoulders = [] + for offset in offsets: + fft_db = self.calc_fft_db(signal, offset) + shoulders.append(self.calc_shoulder(fft_db)) + shoulder = np.mean(shoulders) + + if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot: + self._plot(signal) + + return shoulder + + +# The MIT License (MIT) +# +# Copyright (c) 2017 Andreas Steger +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. diff --git a/dpd/src/const.py b/dpd/src/const.py index daaac5d..275557e 100644 --- a/dpd/src/const.py +++ b/dpd/src/const.py @@ -59,3 +59,5 @@ class const: self.MS_peak_end = self.FFT_end - 100 self.MS_FFT_size = 8192 + self.MS_averaging_size = 4 * self.MS_FFT_size + self.MS_n_averaging = 40 -- cgit v1.2.3