aboutsummaryrefslogtreecommitdiffstats
path: root/dpd/src
diff options
context:
space:
mode:
Diffstat (limited to 'dpd/src')
-rw-r--r--dpd/src/Agc.py2
-rw-r--r--dpd/src/Dab_Util.py10
-rw-r--r--dpd/src/ExtractStatistic.py201
-rw-r--r--dpd/src/MER.py2
-rw-r--r--dpd/src/Measure.py4
-rw-r--r--dpd/src/Model.py389
-rw-r--r--dpd/src/Model_AM.py115
-rw-r--r--dpd/src/Model_PM.py118
-rw-r--r--dpd/src/Model_Poly.py99
-rw-r--r--dpd/src/Symbol_align.py3
-rw-r--r--dpd/src/const.py6
-rw-r--r--dpd/src/phase_align.py2
-rwxr-xr-xdpd/src/subsample_align.py2
13 files changed, 552 insertions, 401 deletions
diff --git a/dpd/src/Agc.py b/dpd/src/Agc.py
index 978b607..b83c91e 100644
--- a/dpd/src/Agc.py
+++ b/dpd/src/Agc.py
@@ -139,7 +139,7 @@ class Agc:
fig.tight_layout()
fig.savefig(fig_path)
- fig.clf()
+ plt.close(fig)
# The MIT License (MIT)
diff --git a/dpd/src/Dab_Util.py b/dpd/src/Dab_Util.py
index e3dbfe3..37be5db 100644
--- a/dpd/src/Dab_Util.py
+++ b/dpd/src/Dab_Util.py
@@ -49,7 +49,7 @@ class Dab_Util:
plt.plot(c, label="corr")
plt.legend()
plt.savefig(corr_path)
- plt.clf()
+ plt.close()
return np.argmax(c) - off + 1
@@ -118,7 +118,7 @@ class Dab_Util:
fig.tight_layout()
fig.savefig(fig_path)
- fig.clf()
+ plt.close(fig)
off_meas = self.lag_upsampling(sig_rx, sig_tx, n_up=1)
off = int(abs(off_meas))
@@ -161,7 +161,7 @@ class Dab_Util:
fig.tight_layout()
fig.savefig(fig_path)
- fig.clf()
+ plt.close(fig)
sig_rx = sa.subsample_align(sig_rx, sig_tx)
@@ -185,7 +185,7 @@ class Dab_Util:
fig.tight_layout()
fig.savefig(fig_path)
- fig.clf()
+ plt.close(fig)
sig_rx = pa.phase_align(sig_rx, sig_tx)
@@ -209,7 +209,7 @@ class Dab_Util:
fig.tight_layout()
fig.savefig(fig_path)
- fig.clf()
+ plt.close(fig)
logging.debug("Sig1_cut: %d %s, Sig2_cut: %d %s, off: %d" % (len(sig_tx), sig_tx.dtype, len(sig_rx), sig_rx.dtype, off))
return sig_tx, sig_rx
diff --git a/dpd/src/ExtractStatistic.py b/dpd/src/ExtractStatistic.py
new file mode 100644
index 0000000..897ec0a
--- /dev/null
+++ b/dpd/src/ExtractStatistic.py
@@ -0,0 +1,201 @@
+# -*- coding: utf-8 -*-
+#
+# DPD Calculation Engine,
+# Extract statistic from data to use in Model
+#
+# http://www.opendigitalradio.org
+# Licence: The MIT License, see notice at the end of this file
+
+import numpy as np
+import pickle
+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
+ assert tx_dpd[0].dtype == np.complex64, \
+ "tx_dpd is not complex64 but {}".format(tx_dpd[0].dtype)
+ assert rx_received[0].dtype == np.complex64, \
+ "rx_received is not complex64 but {}".format(rx_received[0].dtype)
+ # Check if signals have same normalization
+ normalization_error = np.abs(np.median(np.abs(tx_dpd)) -
+ np.median(np.abs(rx_received))) / (
+ np.median(np.abs(tx_dpd)) + np.median(np.abs(rx_received)))
+ assert normalization_error < 0.01, "Non normalized signals"
+
+
+class ExtractStatistic:
+ """Calculate a low variance RX value for equally spaced tx values
+ of a predefined range"""
+
+ def __init__(self,
+ c,
+ plot=False):
+ self.c = c
+
+ self.tx_boundaries = np.linspace(c.ES_start, c.ES_end, c.ES_n_bins + 1)
+ self.n_per_bin = c.ES_n_per_bin
+
+ self.rx_values_lists = []
+ for i in range(c.ES_n_bins):
+ self.rx_values_lists.append([])
+
+ self.tx_values_lists = []
+ for i in range(c.ES_n_bins):
+ self.tx_values_lists.append([])
+
+ self.tx_values = self._tx_value_per_bin()
+
+ self.rx_values = []
+ for i in range(c.ES_n_bins):
+ self.rx_values.append(None)
+
+ self.plot = plot
+
+ def _plot_and_log(self):
+ if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot:
+ phase_diffs_values_lists = self._phase_diff_list_per_bin()
+ phase_diffs_values = self._phase_diff_value_per_bin(phase_diffs_values_lists)
+
+ dt = datetime.datetime.now().isoformat()
+ fig_path = logging_path + "/" + dt + "_ExtractStatistic.png"
+ sub_rows = 3
+ sub_cols = 1
+ fig = plt.figure(figsize=(sub_cols * 6, sub_rows / 2. * 6))
+ i_sub = 0
+
+ i_sub += 1
+ ax = plt.subplot(sub_rows, sub_cols, i_sub)
+ ax.plot(self.tx_values, self.rx_values,
+ label="Estimated Values",
+ color="red")
+ for i, tx_value in enumerate(self.tx_values):
+ rx_values = self.rx_values_lists[i]
+ ax.scatter(np.ones(len(rx_values)) * tx_value,
+ np.abs(rx_values),
+ s=0.1,
+ color="black")
+ ax.set_title("Extracted Statistic")
+ ax.set_xlabel("TX Amplitude")
+ ax.set_ylabel("RX Amplitude")
+ ax.set_ylim(0, 0.8)
+ ax.set_xlim(0, 1.1)
+ ax.legend(loc=4)
+
+ i_sub += 1
+ ax = plt.subplot(sub_rows, sub_cols, i_sub)
+ ax.plot(self.tx_values, np.rad2deg(phase_diffs_values),
+ label="Estimated Values",
+ color="red")
+ for i, tx_value in enumerate(self.tx_values):
+ phase_diff = phase_diffs_values_lists[i]
+ ax.scatter(np.ones(len(phase_diff)) * tx_value,
+ np.rad2deg(phase_diff),
+ s=0.1,
+ color="black")
+ ax.set_xlabel("TX Amplitude")
+ ax.set_ylabel("Phase Difference")
+ ax.set_ylim(-60,60)
+ ax.set_xlim(0, 1.1)
+ ax.legend(loc=4)
+
+ num = []
+ for i, tx_value in enumerate(self.tx_values):
+ rx_values = self.rx_values_lists[i]
+ num.append(len(rx_values))
+ i_sub += 1
+ ax = plt.subplot(sub_rows, sub_cols, i_sub)
+ ax.plot(num)
+ ax.set_xlabel("TX Amplitude")
+ ax.set_ylabel("Number of Samples")
+ ax.set_ylim(0, self.n_per_bin * 1.2)
+
+ fig.tight_layout()
+ fig.savefig(fig_path)
+ plt.close(fig)
+
+ pickle.dump(self.rx_values_lists, open("/tmp/rx_values", "wb"))
+ pickle.dump(self.tx_values, open("/tmp/tx_values", "wb"))
+
+ def _rx_value_per_bin(self):
+ rx_values = []
+ for values in self.rx_values_lists:
+ rx_values.append(np.mean(np.abs(values)))
+ return rx_values
+
+ def _tx_value_per_bin(self):
+ tx_values = []
+ for start, end in zip(self.tx_boundaries, self.tx_boundaries[1:]):
+ tx_values.append(np.mean((start, end)))
+ return tx_values
+
+ def _phase_diff_list_per_bin(self):
+ phase_values_lists = []
+ for tx_list, rx_list in zip(self.tx_values_lists, self.rx_values_lists):
+ phase_diffs = []
+ for tx, rx in zip(tx_list, rx_list):
+ phase_diffs.append(np.angle(rx * tx.conjugate()))
+ phase_values_lists.append(phase_diffs)
+ return phase_values_lists
+
+ def _phase_diff_value_per_bin(self, phase_diffs_values_lists):
+ phase_list = []
+ for values in phase_diffs_values_lists:
+ phase_list.append(np.mean(values))
+ return phase_list
+
+ def extract(self, tx_dpd, rx):
+ _check_input_extract(tx_dpd, rx)
+
+ tx_abs = np.abs(tx_dpd)
+ for i, (tx_start, tx_end) in enumerate(zip(self.tx_boundaries, self.tx_boundaries[1:])):
+ mask = (tx_abs > tx_start) & (tx_abs < tx_end)
+ n_add = max(0, self.n_per_bin - len(self.rx_values_lists[i]))
+ self.rx_values_lists[i] += \
+ list(rx[mask][:n_add])
+ self.tx_values_lists[i] += \
+ list(tx_dpd[mask][:n_add])
+
+ self.rx_values = self._rx_value_per_bin()
+ self.tx_values = self._tx_value_per_bin()
+
+ self._plot_and_log()
+
+ n_per_bin = [len(values) for values in self.rx_values_lists]
+
+ # TODO cleanup
+ phase_diffs_values_lists = self._phase_diff_list_per_bin()
+ phase_diffs_values = self._phase_diff_value_per_bin(phase_diffs_values_lists)
+
+ return np.array(self.tx_values, dtype=np.float32), \
+ np.array(self.rx_values, dtype=np.float32), \
+ np.array(phase_diffs_values, dtype=np.float32), \
+ n_per_bin
+
+# 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/MER.py b/dpd/src/MER.py
index 00fcc23..4f2918e 100644
--- a/dpd/src/MER.py
+++ b/dpd/src/MER.py
@@ -106,7 +106,7 @@ class MER:
plt.tight_layout()
plt.savefig(fig_path)
plt.show()
- plt.clf()
+ plt.close()
MER_res = 20 * np.log10(np.mean([10 ** (MER / 20) for MER in MERs]))
return MER_res
diff --git a/dpd/src/Measure.py b/dpd/src/Measure.py
index 2450b8a..7a9246c 100644
--- a/dpd/src/Measure.py
+++ b/dpd/src/Measure.py
@@ -104,10 +104,6 @@ class Measure:
txframe, tx_ts, rxframe, rx_ts = self.receive_tcp()
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
- dt = datetime.datetime.now().isoformat()
- txframe.tofile(logging_path + "/txframe_" + dt + ".iq")
-
# Normalize received signal with sent signal
rx_median = np.median(np.abs(rxframe))
rxframe = rxframe / rx_median * np.median(np.abs(txframe))
diff --git a/dpd/src/Model.py b/dpd/src/Model.py
index a23f0ce..7ce6171 100644
--- a/dpd/src/Model.py
+++ b/dpd/src/Model.py
@@ -1,388 +1,3 @@
# -*- coding: utf-8 -*-
-#
-# DPD Calculation Engine, model implementation.
-#
-# 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
-from sklearn import linear_model
-
-class PolyModel:
- """Calculates new coefficients using the measurement and the old
- coefficients"""
-
- def __init__(self,
- c,
- SA,
- MER,
- coefs_am,
- coefs_pm,
- learning_rate_am=1.,
- learning_rate_pm=1.,
- plot=False):
- logging.debug("Initialising Poly Model")
- self.c = c
- self.SA = SA
- self.MER = MER
-
- self.learning_rate_am = learning_rate_am
- self.learning_rate_pm = learning_rate_pm
-
- if coefs_am is None:
- self.coefs_am = [1.0, 0, 0, 0, 0]
- else:
- self.coefs_am = coefs_am
- self.coefs_am_history = [coefs_am, ]
- self.mses_am = []
- self.errs_am = []
-
- self.tx_mers = []
- self.rx_mers = []
-
- if coefs_pm is None:
- self.coefs_pm = [0, 0, 0, 0, 0]
- else:
- self.coefs_pm = coefs_pm
- self.coefs_pm_history = [coefs_pm, ]
- self.errs_pm = []
-
- self.plot = plot
-
- def train(self, tx_dpd, rx_received):
- """Give new training data to the model"""
- # Check data type
- assert tx_dpd[0].dtype == np.complex64, \
- "tx_dpd is not complex64 but {}".format(tx_dpd[0].dtype)
- assert rx_received[0].dtype == np.complex64, \
- "rx_received is not complex64 but {}".format(rx_received[0].dtype)
- # Check if signals have same normalization
- normalization_error = np.abs(np.median(np.abs(tx_dpd)) -
- np.median(np.abs(rx_received))) / (
- np.median(np.abs(tx_dpd)) + np.median(np.abs(rx_received)))
- assert normalization_error < 0.01, "Non normalized signals"
-
- tx_choice, rx_choice = self._sample_uniformly(tx_dpd, rx_received)
- new_coefs_am = self._next_am_coefficent(tx_choice, rx_choice)
- new_coefs_pm, phase_diff_choice = self._next_pm_coefficent(tx_choice, rx_choice)
-
- logging.debug('txframe: min {:.2f}, max {:.2f}, ' \
- 'median {:.2f}; rxframe: min {:.2f}, max {:.2f}, ' \
- 'median {:.2f}; new coefs_am {};' \
- 'new_coefs_pm {}'.format(
- np.min(np.abs(tx_dpd)),
- np.max(np.abs(tx_dpd)),
- np.median(np.abs(tx_dpd)),
- np.min(np.abs(rx_choice)),
- np.max(np.abs(rx_choice)),
- np.median(np.abs(rx_choice)),
- new_coefs_am,
- new_coefs_pm))
-
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot:
- off = self.SA.calc_offset(tx_dpd)
- tx_mer = self.MER.calc_mer(tx_dpd[off:off + self.c.T_U])
- rx_mer = self.MER.calc_mer(rx_received[off:off + self.c.T_U], debug=True)
- self.tx_mers.append(tx_mer)
- self.rx_mers.append(rx_mer)
-
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot:
- dt = datetime.datetime.now().isoformat()
- fig_path = logging_path + "/" + dt + "_Model.svg"
-
- fig = plt.figure(figsize=(2 * 6, 2 * 6))
-
- i_sub = 1
-
- ax = plt.subplot(4, 2, i_sub)
- i_sub += 1
- ax.plot(np.abs(tx_dpd[:128]),
- label="TX sent",
- linestyle=":")
- ax.plot(np.abs(rx_received[:128]),
- label="RX received",
- color="red")
- ax.set_title("Synchronized Signals of Iteration {}"
- .format(len(self.coefs_am_history)))
- ax.set_xlabel("Samples")
- ax.set_ylabel("Amplitude")
- ax.text(0, 0, "TX (max {:01.3f}, mean {:01.3f}, " \
- "median {:01.3f})".format(
- np.max(np.abs(tx_dpd)),
- np.mean(np.abs(tx_dpd)),
- np.median(np.abs(tx_dpd))
- ), size=8)
- ax.legend(loc=4)
-
- ax = plt.subplot(4, 2, i_sub)
- i_sub += 1
- ccdf_min, ccdf_max = 0, 1
- tx_hist, ccdf_edges = np.histogram(np.abs(tx_dpd),
- bins=60,
- range=(ccdf_min, ccdf_max))
- tx_hist_normalized = tx_hist.astype(float) / np.sum(tx_hist)
- ccdf = 1.0 - np.cumsum(tx_hist_normalized)
- ax.semilogy(ccdf_edges[:-1], ccdf, label="CCDF")
- ax.semilogy(ccdf_edges[:-1],
- tx_hist_normalized,
- label="Histogram",
- drawstyle='steps')
- ax.legend(loc=4)
- ax.set_ylim(1e-5, 2)
- ax.set_title("Complementary Cumulative Distribution Function")
- ax.set_xlabel("TX Amplitude")
- ax.set_ylabel("Ratio of Samples larger than x")
-
- ax = plt.subplot(4, 2, i_sub)
- i_sub += 1
- ax.semilogy(np.array(self.mses_am) + 1e-10, label="log(MSE)")
- ax.semilogy(np.array(self.errs_am) + 1e-10, label="log(ERR)")
- ax.legend(loc=4)
- ax.set_title("MSE History")
- ax.set_xlabel("Iterations")
- ax.set_ylabel("MSE")
-
- ax = plt.subplot(4, 2, i_sub)
- i_sub += 1
- ax.plot(self.tx_mers, label="TX MER")
- ax.plot(self.rx_mers, label="RX MER")
- ax.legend(loc=4)
- ax.set_title("MER History")
- ax.set_xlabel("Iterations")
- ax.set_ylabel("MER")
-
- ax = plt.subplot(4, 2, i_sub)
- rx_range = np.linspace(0, 1, num=100, dtype=np.complex64)
- rx_range_dpd = self._dpd_amplitude(rx_range)[0]
- rx_range_dpd_new = self._dpd_amplitude(rx_range, new_coefs_am)[0]
- i_sub += 1
- ax.scatter(
- np.abs(tx_choice),
- np.abs(rx_choice),
- s=0.1)
- ax.plot(rx_range_dpd / self.coefs_am[0], rx_range, linewidth=0.25, label="current")
- ax.plot(rx_range_dpd_new / self.coefs_am[0], rx_range, linewidth=0.25, label="next")
- ax.set_ylim(0, 1)
- ax.set_xlim(0, 1)
- ax.legend()
- ax.set_title("Amplifier Characteristic")
- ax.set_xlabel("TX Amplitude")
- ax.set_ylabel("RX Amplitude")
-
- ax = plt.subplot(4, 2, i_sub)
- i_sub += 1
- coefs_am_history = np.array(self.coefs_am_history)
- for idx, coef_hist in enumerate(coefs_am_history.T):
- ax.plot(coef_hist,
- label="Coef {}".format(idx),
- linewidth=0.5)
- ax.legend(loc=4)
- ax.set_title("AM/AM Coefficient History")
- ax.set_xlabel("Iterations")
- ax.set_ylabel("Coefficient Value")
-
- phase_range = np.linspace(0, 1, num=100, dtype=np.complex64)
- phase_range_dpd = self._dpd_phase(phase_range)[0]
- phase_range_dpd_new = self._dpd_phase(phase_range,
- coefs=new_coefs_pm)[0]
- ax = plt.subplot(4, 2, i_sub)
- i_sub += 1
- ax.scatter(
- np.abs(tx_choice),
- np.rad2deg(phase_diff_choice),
- s=0.1)
- ax.plot(
- np.abs(phase_range),
- np.rad2deg(phase_range_dpd),
- linewidth=0.25,
- label="current")
- ax.plot(
- np.abs(phase_range),
- np.rad2deg(phase_range_dpd_new),
- linewidth=0.25,
- label="next")
- ax.set_ylim(-60, 60)
- ax.set_xlim(0, 1)
- ax.legend()
- ax.set_title("Amplifier Characteristic")
- ax.set_xlabel("TX Amplitude")
- ax.set_ylabel("Phase Difference")
-
- ax = plt.subplot(4, 2, i_sub)
- i_sub += 1
- coefs_pm_history = np.array(self.coefs_pm_history)
- for idx, coef_phase_hist in enumerate(coefs_pm_history.T):
- ax.plot(coef_phase_hist,
- label="Coef {}".format(idx),
- linewidth=0.5)
- ax.legend(loc=4)
- ax.set_title("AM/PM Coefficient History")
- ax.set_xlabel("Iterations")
- ax.set_ylabel("Coefficient Value")
-
- fig.tight_layout()
- fig.savefig(fig_path)
- fig.clf()
-
- self.coefs_am = new_coefs_am
- self.coefs_am_history.append(self.coefs_am)
- self.coefs_pm = new_coefs_pm
- self.coefs_pm_history.append(self.coefs_pm)
-
- def get_dpd_data(self):
- return "poly", self.coefs_am, self.coefs_pm
-
- def _sample_uniformly(self, tx_dpd, rx_received, n_bins=5):
- """This function returns tx and rx samples in a way
- that the tx amplitudes have an approximate uniform
- distribution with respect to the tx_dpd amplitudes"""
- mask = np.logical_and((np.abs(tx_dpd) > 0.01), (np.abs(rx_received) > 0.01))
- tx_dpd = tx_dpd[mask]
- rx_received = rx_received[mask]
-
- txframe_aligned_abs = np.abs(tx_dpd)
- ccdf_min = 0
- ccdf_max = np.max(txframe_aligned_abs)
- tx_hist, ccdf_edges = np.histogram(txframe_aligned_abs,
- bins=n_bins,
- range=(ccdf_min, ccdf_max))
- n_choise = np.min(tx_hist)
- tx_choice = np.zeros(n_choise * n_bins, dtype=np.complex64)
- rx_choice = np.zeros(n_choise * n_bins, dtype=np.complex64)
-
- for idx, bin in enumerate(tx_hist):
- indices = np.where((txframe_aligned_abs >= ccdf_edges[idx]) &
- (txframe_aligned_abs <= ccdf_edges[idx + 1]))[0]
- indices_choise = np.random.choice(indices,
- n_choise,
- replace=False)
- rx_choice[idx * n_choise:(idx + 1) * n_choise] = \
- rx_received[indices_choise]
- tx_choice[idx * n_choise:(idx + 1) * n_choise] = \
- tx_dpd[indices_choise]
-
- assert isinstance(rx_choice[0], np.complex64), \
- "rx_choice is not complex64 but {}".format(rx_choice[0].dtype)
- assert isinstance(tx_choice[0], np.complex64), \
- "tx_choice is not complex64 but {}".format(tx_choice[0].dtype)
-
- return tx_choice, rx_choice
-
- def _dpd_amplitude(self, sig, coefs=None):
- if coefs is None:
- coefs = self.coefs_am
- assert isinstance(sig[0], np.complex64), "Sig is not complex64 but {}".format(sig[0].dtype)
- sig_abs = np.abs(sig)
- A_sig = np.vstack([np.ones(sig_abs.shape),
- sig_abs ** 1,
- sig_abs ** 2,
- sig_abs ** 3,
- sig_abs ** 4,
- ]).T
- sig_dpd = sig * np.sum(A_sig * coefs, axis=1)
- return sig_dpd, A_sig
-
- def _dpd_phase(self, sig, coefs=None):
- if coefs is None:
- coefs = self.coefs_pm
- assert isinstance(sig[0], np.complex64), "Sig is not complex64 but {}".format(sig[0].dtype)
- sig_abs = np.abs(sig)
- A_phase = np.vstack([np.ones(sig_abs.shape),
- sig_abs ** 1,
- sig_abs ** 2,
- sig_abs ** 3,
- sig_abs ** 4,
- ]).T
- phase_diff_est = np.sum(A_phase * coefs, axis=1)
- return phase_diff_est, A_phase
-
- def _next_am_coefficent(self, tx_choice, rx_choice):
- """Calculate new coefficients for AM/AM correction"""
- rx_dpd, rx_A = self._dpd_amplitude(rx_choice)
- rx_dpd = rx_dpd * (
- np.median(np.abs(tx_choice)) /
- np.median(np.abs(rx_dpd)))
- err = np.abs(rx_dpd) - np.abs(tx_choice)
- mse = np.mean(np.abs((rx_dpd - tx_choice) ** 2))
- self.mses_am.append(mse)
- self.errs_am.append(np.mean(err**2))
-
- reg = linear_model.Ridge(alpha=0.00001)
- reg.fit(rx_A, err)
- a_delta = reg.coef_
- new_coefs_am = self.coefs_am - self.learning_rate_am * a_delta
- new_coefs_am = new_coefs_am * (self.coefs_am[0] / new_coefs_am[0])
- return new_coefs_am
-
- def _next_pm_coefficent(self, tx_choice, rx_choice):
- """Calculate new coefficients for AM/PM correction
- Assuming deviations smaller than pi/2"""
- phase_diff_choice = np.angle(
- (rx_choice * tx_choice.conjugate()) /
- (np.abs(rx_choice) * np.abs(tx_choice))
- )
- plt.hist(phase_diff_choice)
- plt.savefig('/tmp/hist_' + str(np.random.randint(0,1000)) + '.svg')
- plt.clf()
- phase_diff_est, phase_A = self._dpd_phase(rx_choice)
- err_phase = phase_diff_est - phase_diff_choice
- self.errs_pm.append(np.mean(np.abs(err_phase ** 2)))
-
- reg = linear_model.Ridge(alpha=0.00001)
- reg.fit(phase_A, err_phase)
- p_delta = reg.coef_
- new_coefs_pm = self.coefs_pm - self.learning_rate_pm * p_delta
-
- return new_coefs_pm, phase_diff_choice
-
-class LutModel:
- """Implements a model that calculates lookup table coefficients"""
-
- def __init__(self,
- c,
- SA,
- MER,
- learning_rate=1.,
- plot=False):
- logging.debug("Initialising LUT Model")
- self.c = c
- self.SA = SA
- self.MER = MER
- self.learning_rate = learning_rate
- self.plot = plot
-
- def train(self, tx_dpd, rx_received):
- pass
-
- def get_dpd_data(self):
- return ("lut", np.ones(32, dtype=np.complex64))
-
-# The MIT License (MIT)
-#
-# Copyright (c) 2017 Andreas Steger
-# Copyright (c) 2017 Matthias P. Braendli
-#
-# 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.
+from src.Model_Poly import Poly
+from src.Model_Lut import Lut
diff --git a/dpd/src/Model_AM.py b/dpd/src/Model_AM.py
new file mode 100644
index 0000000..ef6cc6c
--- /dev/null
+++ b/dpd/src/Model_AM.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+#
+# DPD Calculation Engine, model implementation for Amplitude and not Phase
+#
+# 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
+from sklearn import linear_model
+
+
+def check_input_get_next_coefs(tx_dpd, rx_received):
+ is_float32 = lambda x: (isinstance(x, np.ndarray) and
+ x.dtype == np.float32 and
+ x.flags.contiguous)
+ assert is_float32(tx_dpd), \
+ "tx_dpd is not float32 but {}".format(tx_dpd[0].dtype)
+ assert is_float32(rx_received), \
+ "rx_received is not float32 but {}".format(tx_dpd[0].dtype)
+
+
+class Model_AM:
+ """Calculates new coefficients using the measurement and the previous
+ coefficients"""
+
+ def __init__(self,
+ c,
+ learning_rate_am=0.1,
+ plot=False):
+ self.c = c
+
+ self.learning_rate_am = learning_rate_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:
+ tx_range, rx_est = self.calc_line(coefs_am, 0, 0.6)
+ tx_range_new, rx_est_new = self.calc_line(coefs_am_new, 0, 0.6)
+
+ dt = datetime.datetime.now().isoformat()
+ fig_path = logging_path + "/" + dt + "_Model_AM.svg"
+ sub_rows = 1
+ sub_cols = 1
+ fig = plt.figure(figsize=(sub_cols * 6, sub_rows / 2. * 6))
+ i_sub = 0
+
+ i_sub += 1
+ ax = plt.subplot(sub_rows, sub_cols, i_sub)
+ ax.plot(tx_range, rx_est,
+ label="Estimated TX",
+ alpha=0.3,
+ color="gray")
+ ax.plot(tx_range_new, rx_est_new,
+ label="New Estimated TX",
+ color="red")
+ ax.scatter(tx_dpd, rx_received,
+ label="Binned Data",
+ color="blue",
+ s=0.1)
+ ax.set_title("Model_AM")
+ ax.set_xlabel("TX Amplitude")
+ ax.set_ylabel("RX Amplitude")
+ ax.legend(loc=4)
+
+ fig.tight_layout()
+ fig.savefig(fig_path)
+ plt.close(fig)
+
+ def poly(self, sig):
+ return np.array([sig ** i for i in range(1, 6)]).T
+
+ def fit_poly(self, tx_abs, rx_abs):
+ return np.linalg.lstsq(self.poly(rx_abs), tx_abs)[0]
+
+ def calc_line(self, coefs, min_amp, max_amp):
+ rx_range = np.linspace(min_amp, max_amp)
+ tx_est = np.sum(self.poly(rx_range) * coefs, axis=1)
+ return tx_est, rx_range
+
+ def get_next_coefs(self, tx_dpd, rx_received, coefs_am):
+ check_input_get_next_coefs(tx_dpd, rx_received)
+
+ coefs_am_new = self.fit_poly(tx_dpd, rx_received)
+ self._plot(tx_dpd, rx_received, coefs_am, coefs_am_new)
+
+ return coefs_am_new
+
+# 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/Model_PM.py b/dpd/src/Model_PM.py
new file mode 100644
index 0000000..6639382
--- /dev/null
+++ b/dpd/src/Model_PM.py
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+#
+# DPD Calculation Engine, model implementation for Amplitude and not Phase
+#
+# 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
+from sklearn import linear_model
+
+
+def check_input_get_next_coefs(tx_dpd, phase_diff):
+ is_float32 = lambda x: (isinstance(x, np.ndarray) and
+ x.dtype == np.float32 and
+ x.flags.contiguous)
+ assert is_float32(tx_dpd), \
+ "tx_dpd is not float32 but {}".format(tx_dpd[0].dtype)
+ assert is_float32(phase_diff), \
+ "phase_diff is not float32 but {}".format(tx_dpd[0].dtype)
+ assert tx_dpd.shape == phase_diff.shape, \
+ "tx_dpd.shape {}, phase_diff.shape {}".format(
+ tx_dpd.shape, phase_diff.shape)
+
+
+class Model_PM:
+ """Calculates new coefficients using the measurement and the previous
+ coefficients"""
+
+ def __init__(self,
+ c,
+ learning_rate_pm=0.1,
+ plot=False):
+ self.c = c
+
+ self.learning_rate_pm = learning_rate_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:
+ 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"
+ sub_rows = 1
+ sub_cols = 1
+ fig = plt.figure(figsize=(sub_cols * 6, sub_rows / 2. * 6))
+ i_sub = 0
+
+ i_sub += 1
+ ax = plt.subplot(sub_rows, sub_cols, i_sub)
+ ax.plot(tx_range, phase_diff_est,
+ label="Estimated Phase Diff",
+ alpha=0.3,
+ color="gray")
+ ax.plot(tx_range_new, phase_diff_est_new,
+ label="New Estimated Phase Diff",
+ color="red")
+ ax.scatter(tx_dpd, phase_diff,
+ label="Binned Data",
+ color="blue",
+ s=0.1)
+ ax.set_title("Model_PM")
+ ax.set_xlabel("TX Amplitude")
+ ax.set_ylabel("Phase DIff")
+ ax.legend(loc=4)
+
+ fig.tight_layout()
+ fig.savefig(fig_path)
+ plt.close(fig)
+
+ def poly(self, sig):
+ return np.array([sig ** i for i in range(0, 5)]).T
+
+ def fit_poly(self, tx_abs, phase_diff):
+ return np.linalg.lstsq(self.poly(tx_abs), phase_diff)[0]
+
+ def calc_line(self, coefs, min_amp, max_amp):
+ tx_range = np.linspace(min_amp, max_amp)
+ phase_diff = np.sum(self.poly(tx_range) * coefs, axis=1)
+ return tx_range, phase_diff
+
+ def get_next_coefs(self, tx_dpd, phase_diff, coefs_pm):
+ check_input_get_next_coefs(tx_dpd, phase_diff)
+
+ coefs_pm_new = self.fit_poly(tx_dpd, phase_diff)
+ self._plot(tx_dpd, phase_diff, coefs_pm, coefs_pm_new)
+
+ return coefs_pm_new
+
+# 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/Model_Poly.py b/dpd/src/Model_Poly.py
new file mode 100644
index 0000000..f6c024c
--- /dev/null
+++ b/dpd/src/Model_Poly.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+#
+# DPD Calculation Engine, model implementation using polynomial
+#
+# 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
+from sklearn import linear_model
+
+import src.Model_AM as Model_AM
+import src.Model_PM as Model_PM
+
+
+def assert_np_float32(x):
+ assert isinstance(x, np.ndarray)
+ assert x.dtype == np.float32
+ assert x.flags.contiguous
+
+def _check_input_get_next_coefs(tx_abs, rx_abs, phase_diff):
+ assert_np_float32(tx_abs)
+ assert_np_float32(rx_abs)
+ assert_np_float32(phase_diff)
+
+ assert tx_abs.shape == rx_abs.shape, \
+ "tx_abs.shape {}, rx_abs.shape {}".format(
+ tx_abs.shape, rx_abs.shape)
+ assert tx_abs.shape == phase_diff.shape, \
+ "tx_abs.shape {}, phase_diff.shape {}".format(
+ tx_abs.shape, phase_diff.shape)
+
+
+class Poly:
+ """Calculates new coefficients using the measurement and the previous
+ coefficients"""
+
+ def __init__(self,
+ c,
+ learning_rate_am=1.0,
+ learning_rate_pm=1.0,
+ plot=False):
+ self.c = c
+
+ self.learning_rate_am = learning_rate_am
+ self.learning_rate_pm = learning_rate_pm
+
+ self.reset_coefs()
+
+ self.model_am = Model_AM.Model_AM(c, plot=True)
+ self.model_pm = Model_PM.Model_PM(c, plot=True)
+
+ self.plot = 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):
+ _check_input_get_next_coefs(tx_abs, rx_abs, phase_diff)
+
+ coefs_am_new = self.model_am.get_next_coefs(tx_abs, rx_abs, self.coefs_am)
+ coefs_pm_new = self.model_pm.get_next_coefs(tx_abs, phase_diff, self.coefs_pm)
+
+ self.coefs_am = self.coefs_am + (coefs_am_new - self.coefs_am) * self.learning_rate_am
+ self.coefs_pm = self.coefs_pm + (coefs_pm_new - self.coefs_pm) * self.learning_rate_pm
+
+ def get_dpd_data(self):
+ return "poly", self.coefs_am, self.coefs_pm
+
+# 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/Symbol_align.py b/dpd/src/Symbol_align.py
index 05a9049..6c814a8 100644
--- a/dpd/src/Symbol_align.py
+++ b/dpd/src/Symbol_align.py
@@ -147,7 +147,8 @@ class Symbol_align:
delta_sample_int = np.round(delta_sample).astype(int)
error = np.abs(delta_sample_int - delta_sample)
if error > 0.1:
- raise RuntimeError("Could not calculate sample offset")
+ raise RuntimeError("Could not calculate " \
+ "sample offset. Error {}".format(error))
return delta_sample_int
def calc_offset(self, tx):
diff --git a/dpd/src/const.py b/dpd/src/const.py
index 1011c6c..75ff819 100644
--- a/dpd/src/const.py
+++ b/dpd/src/const.py
@@ -36,3 +36,9 @@ class const:
# 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
+
+ # Constants for ExtractStatistic
+ self.ES_start = 0.0
+ self.ES_end = 1.0
+ self.ES_n_bins = 64
+ self.ES_n_per_bin = 256
diff --git a/dpd/src/phase_align.py b/dpd/src/phase_align.py
index 7f82392..7317d70 100644
--- a/dpd/src/phase_align.py
+++ b/dpd/src/phase_align.py
@@ -75,7 +75,7 @@ def phase_align(sig, ref_sig, plot=False):
plt.legend(loc=4)
plt.tight_layout()
plt.savefig(fig_path)
- plt.clf()
+ plt.close()
return sig
diff --git a/dpd/src/subsample_align.py b/dpd/src/subsample_align.py
index 6d1cd2a..b0cbe88 100755
--- a/dpd/src/subsample_align.py
+++ b/dpd/src/subsample_align.py
@@ -78,7 +78,7 @@ def subsample_align(sig, ref_sig, plot=False):
plt.plot(ixs, taus)
plt.title("Subsample correlation, minimum is best: {}".format(best_tau))
plt.savefig(tau_path)
- plt.clf()
+ plt.close()
# Prepare rotate_vec = fft_sig with rotated phase
rotate_vec = np.exp(1j * best_tau * omega)