From cfa9461f269e616d6d54658d583b37d215f35a7b Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Wed, 28 Nov 2018 11:11:22 +0100 Subject: GUI: Add part of calibration routine --- gui/api/__init__.py | 19 +++++++++++++- gui/dpd/Capture.py | 51 ++++++++++++++++++++++---------------- gui/dpd/__init__.py | 14 +++++++++++ gui/run.py | 5 ++++ gui/static/js/odr-predistortion.js | 12 +++++++++ gui/templates/predistortion.html | 20 +++++++++++++-- 6 files changed, 96 insertions(+), 25 deletions(-) (limited to 'gui') diff --git a/gui/api/__init__.py b/gui/api/__init__.py index a535eb3..56062c3 100755 --- a/gui/api/__init__.py +++ b/gui/api/__init__.py @@ -47,15 +47,20 @@ class API(plugins.SimplePlugin): plugins.SimplePlugin.__init__(self, bus) self.mod_rc = mod_rc self.dpd_state = None + self.calibration_result = None def start(self): self.bus.subscribe("dpd-state", self.dpd_state) + self.bus.subscribe("dpd-calibration-result", self.calibration_result) def stop(self): self.bus.unsubscribe("dpd-state", self.dpd_state) + self.bus.unsubscribe("dpd-calibration-result", self.calibration_result) + + def calibration_result(self, new_result): + self.calibration_result = new_result def dpd_state(self, new_state): - print("API got new dpd-state {}".format(new_state)) self.dpd_state = new_state @cherrypy.expose @@ -102,3 +107,15 @@ class API(plugins.SimplePlugin): else: return send_error("DPD state unknown") + @cherrypy.expose + @cherrypy.tools.json_out() + def dpd_calibrate(self, **kwargs): + if cherrypy.request.method == 'POST': + cherrypy.engine.publish('dpd-calibrate', None) + return send_ok() + else: + if self.dpd_state is not None: + return send_ok(self.calibration_result) + else: + return send_error("DPD calibration result unknown") + diff --git a/gui/dpd/Capture.py b/gui/dpd/Capture.py index e2ac63d..4c0e99c 100644 --- a/gui/dpd/Capture.py +++ b/gui/dpd/Capture.py @@ -36,6 +36,9 @@ import io from . import Align as sa +def correlation_coefficient(sig_tx, sig_rx): + return np.corrcoef(sig_tx, sig_rx)[0, 1] + def align_samples(sig_tx, sig_rx): """ Returns an aligned version of sig_tx and sig_rx by cropping, subsample alignment and @@ -61,7 +64,7 @@ def align_samples(sig_tx, sig_rx): # Fine subsample alignment and phase offset sig_rx = sa.subsample_align(sig_rx, sig_tx) sig_rx = sa.phase_align(sig_rx, sig_tx) - return sig_tx, sig_rx + return sig_tx, sig_rx, abs(off_meas) class Capture: """Capture samples from ODR-DabMod""" @@ -76,14 +79,16 @@ class Capture: # samples to avoid that the polynomial gets overfitted in the low-amplitude # part, which is less interesting than the high-amplitude part, where # non-linearities become apparent. - self.binning_start = 0.0 - self.binning_end = 1.0 self.binning_n_bins = 64 # Number of bins between binning_start and binning_end self.binning_n_per_bin = 128 # Number of measurements pre bin - self.target_median = 0.05 - self.median_max = self.target_median * 1.4 - self.median_min = self.target_median / 1.4 + self.rx_normalisation = 1.0 + + self.clear_accumulated() + + def clear_accumulated(self): + self.binning_start = 0.0 + self.binning_end = 1.0 # axis 0: bins # axis 1: 0=tx, 1=rx @@ -156,30 +161,32 @@ class Capture: return txframe, tx_ts, rxframe, rx_ts - def get_samples(self): - """Connect to ODR-DabMod, retrieve TX and RX samples, load - into numpy arrays, and return a tuple - (txframe_aligned, tx_ts, tx_median, rxframe_aligned, rx_ts, rx_median) - """ - + def calibrate(self): txframe, tx_ts, rxframe, rx_ts = self.receive_tcp() # Normalize received signal with sent signal tx_median = np.median(np.abs(txframe)) + rx_median = np.median(np.abs(rxframe)) + self.rx_normalisation = tx_median / rx_median - if self.median_max < tx_median: - raise ValueError("TX median {} too high, decrease digital_gain!".format(tx_median)) - elif tx_median < self.median_min: - raise ValueError("TX median {} too low, increase digital_gain!".format(tx_median)) - else: - rx_median = np.median(np.abs(rxframe)) - rxframe = rxframe / rx_median * tx_median + rxframe = rxframe * self.rx_normalisation + txframe_aligned, rxframe_aligned, coarse_offset = align_samples(txframe, rxframe) - txframe_aligned, rxframe_aligned = align_samples(txframe, rxframe) + return tx_ts, tx_median, rx_ts, rx_median, coarse_offset, correlation_coefficient(txframe_aligned, rxframe_aligned) + + def get_samples(self): + """Connect to ODR-DabMod, retrieve TX and RX samples, load + into numpy arrays, and return a tuple + (txframe_aligned, tx_ts, tx_median, rxframe_aligned, rx_ts, rx_median) + """ - self._bin_and_accumulate(txframe_aligned, rxframe_aligned) + txframe, tx_ts, rxframe, rx_ts = self.receive_tcp() - return txframe_aligned, tx_ts, tx_median, rxframe_aligned, rx_ts, rx_median + # Normalize received signal with calibrated normalisation + rxframe = rxframe * self.rx_normalisation + txframe_aligned, rxframe_aligned, coarse_offset = align_samples(txframe, rxframe) + self._bin_and_accumulate(txframe_aligned, rxframe_aligned) + return txframe_aligned, tx_ts, tx_median, rxframe_aligned, rx_ts, rx_median def bin_histogram(self): return [b.shape[0] for b in self.accumulated_bins] diff --git a/gui/dpd/__init__.py b/gui/dpd/__init__.py index 06d180d..716b8c2 100644 --- a/gui/dpd/__init__.py +++ b/gui/dpd/__init__.py @@ -23,6 +23,7 @@ # along with ODR-DabMod. If not, see . from . import Capture +import numpy as np class DPD: def __init__(self, samplerate=8192000): @@ -50,6 +51,19 @@ class DPD: def pointcloud_png(self): return self.capture.pointcloud_png() + def clear_accumulated(self): + return self.capture.clear_accumulated() + + def capture_calibration(self): + tx_ts, tx_median, rx_ts, rx_median, coarse_offset, correlation_coefficient = self.capture.calibrate() + result = {'status': "ok"} + result['length'] = len(txframe_aligned) + result['tx_median'] = "{:.2}dB".format(20*np.log10(tx_median)) + result['rx_median'] = "{:.2}dB".format(20*np.log10(rx_median)) + result['tx_ts'] = tx_ts + result['rx_ts'] = rx_ts + result['correlation'] = correlation_coefficient + def capture_samples(self): """Captures samples and store them in the accumulated samples, returns a dict with some info""" diff --git a/gui/run.py b/gui/run.py index b4af742..dea3222 100755 --- a/gui/run.py +++ b/gui/run.py @@ -82,13 +82,18 @@ class DPDPlugin(plugins.SimplePlugin): def start(self): self.bus.subscribe("dpd-capture", self.trigger_capture) + self.bus.subscribe("dpd-calibrate", self.trigger_calibrate) def stop(self): self.bus.unsubscribe("dpd-capture", self.trigger_capture) + self.bus.unsubscribe("dpd-calibrate", self.trigger_calibrate) def trigger_capture(self, param): print("trigger_capture({})".format(param)) + def trigger_calibrate(self, param): + cherrypy.engine.publish('dpd-calibration-result', self.dpd.capture_calibration()) + if __name__ == '__main__': parser = argparse.ArgumentParser(description='ODR-DabMod Web GUI') parser.add_argument('-c', '--config', diff --git a/gui/static/js/odr-predistortion.js b/gui/static/js/odr-predistortion.js index 4e86f1c..01992d7 100644 --- a/gui/static/js/odr-predistortion.js +++ b/gui/static/js/odr-predistortion.js @@ -19,6 +19,18 @@ // along with ODR-DabMod. If not, see . $(function(){ + $('#calibrate').click(function() { + doApiRequestPOST("/api/calibrate", {}, function(data) { + console.log("calibrate succeeded: " + JSON.stringify(data)); + + setTimeout(function() { + doApiRequestGET("/api/calibrate", function(data) { + $('#calibrationresults').text(data); + }); + }, 2000); + }); + }); + $('#capturebutton').click(function() { doApiRequestPOST("/api/trigger_capture", {}, function(data) { console.log("trigger_capture succeeded: " + JSON.stringify(data)); diff --git a/gui/templates/predistortion.html b/gui/templates/predistortion.html index ed224d8..139d982 100644 --- a/gui/templates/predistortion.html +++ b/gui/templates/predistortion.html @@ -29,16 +29,31 @@ along with ODR-DabMod. If not, see .
+
Calibration
+
+
Calibration needs to be done once before the PA model + can be trained. Every time calibration is changed, the predistortion + parameters are invalidated!
+ +
Calibration results:N/A
+
+
+ +
-- cgit v1.2.3