From d5cbe10c0e2298b0e40161607a3da158249bdb82 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Tue, 4 Dec 2018 10:18:33 +0100 Subject: Move python stuff to folder --- README.md | 6 +- dpd/README.md | 264 - dpd/apply_adapt_dumps.py | 75 - dpd/dpd.ini | 60 - dpd/img/setup_diagram.svg | 395 - dpd/img/setup_photo.svg | 184 - dpd/img/shoulder_measurement_after.png | Bin 368619 -> 0 bytes dpd/img/shoulder_measurement_before.png | Bin 381174 -> 0 bytes dpd/iq_file_server.py | 120 - dpd/lut.coef | 64 - dpd/main.py | 336 - dpd/poly.coef | 12 - dpd/show_spectrum.py | 276 - dpd/src/Adapt.py | 286 - dpd/src/Dab_Util.py | 246 - dpd/src/ExtractStatistic.py | 196 - dpd/src/GlobalConfig.py | 108 - dpd/src/Heuristics.py | 56 - dpd/src/MER.py | 132 - dpd/src/Measure.py | 136 - dpd/src/Measure_Shoulders.py | 158 - dpd/src/Model.py | 32 - dpd/src/Model_AM.py | 122 - dpd/src/Model_Lut.py | 60 - dpd/src/Model_PM.py | 124 - dpd/src/Model_Poly.py | 101 - dpd/src/RX_Agc.py | 166 - dpd/src/Symbol_align.py | 193 - dpd/src/TX_Agc.py | 131 - dpd/src/__init__.py | 0 dpd/src/phase_align.py | 98 - dpd/src/subsample_align.py | 111 - dpd/store_received.py | 85 - gui/README.md | 45 - gui/api/__init__.py | 132 - gui/configuration.py | 44 - gui/dpd/Align.py | 166 - gui/dpd/Capture.py | 253 - gui/dpd/__init__.py | 93 - gui/logs/.keep | 0 gui/run.py | 166 - gui/static/css/bootstrap.min.css | 5 - gui/static/css/jquery.gritter.css | 100 - gui/static/dpd/index.txt | 1 - gui/static/fonts/accept.png | Bin 1202 -> 0 bytes gui/static/fonts/favicon.ico | Bin 318 -> 0 bytes gui/static/fonts/glyphicons-halflings-regular.eot | Bin 20127 -> 0 bytes gui/static/fonts/glyphicons-halflings-regular.svg | 288 - gui/static/fonts/glyphicons-halflings-regular.ttf | Bin 45404 -> 0 bytes gui/static/fonts/glyphicons-halflings-regular.woff | Bin 23424 -> 0 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 18028 -> 0 bytes gui/static/fonts/gritter-light.png | Bin 4899 -> 0 bytes gui/static/fonts/gritter.png | Bin 4880 -> 0 bytes gui/static/fonts/ie-spacer.gif | Bin 43 -> 0 bytes gui/static/fonts/warning.png | Bin 1418 -> 0 bytes gui/static/js/bootstrap.min.js | 7 - gui/static/js/jquery.gritter.js | 408 - gui/static/js/jquery.js | 9205 -------------------- gui/static/js/odr-modulator.js | 80 - gui/static/js/odr-predistortion.js | 99 - gui/static/js/odr-rcvalues.js | 82 - gui/static/js/odr.js | 109 - gui/templates/about.html | 65 - gui/templates/body-nav.html | 39 - gui/templates/head.html | 25 - gui/templates/home.html | 35 - gui/templates/modulator.html | 73 - gui/templates/predistortion.html | 88 - gui/templates/rcvalues.html | 48 - gui/ui-config.json | 9 - gui/zmqrc.py | 84 - python/dpd/README.md | 264 + python/dpd/apply_adapt_dumps.py | 75 + python/dpd/dpd.ini | 60 + python/dpd/img/setup_diagram.svg | 395 + python/dpd/img/setup_photo.svg | 184 + python/dpd/img/shoulder_measurement_after.png | Bin 0 -> 368619 bytes python/dpd/img/shoulder_measurement_before.png | Bin 0 -> 381174 bytes python/dpd/iq_file_server.py | 120 + python/dpd/lut.coef | 64 + python/dpd/main.py | 338 + python/dpd/poly.coef | 12 + python/dpd/show_spectrum.py | 276 + python/dpd/src/Adapt.py | 286 + python/dpd/src/Dab_Util.py | 246 + python/dpd/src/ExtractStatistic.py | 196 + python/dpd/src/GlobalConfig.py | 108 + python/dpd/src/Heuristics.py | 56 + python/dpd/src/MER.py | 132 + python/dpd/src/Measure.py | 136 + python/dpd/src/Measure_Shoulders.py | 158 + python/dpd/src/Model.py | 32 + python/dpd/src/Model_AM.py | 122 + python/dpd/src/Model_Lut.py | 60 + python/dpd/src/Model_PM.py | 124 + python/dpd/src/Model_Poly.py | 101 + python/dpd/src/RX_Agc.py | 166 + python/dpd/src/Symbol_align.py | 193 + python/dpd/src/TX_Agc.py | 131 + python/dpd/src/__init__.py | 0 python/dpd/src/phase_align.py | 98 + python/dpd/src/subsample_align.py | 111 + python/dpd/store_received.py | 85 + python/gui/README.md | 45 + python/gui/api/__init__.py | 132 + python/gui/configuration.py | 44 + python/gui/dpd/Align.py | 166 + python/gui/dpd/Capture.py | 253 + python/gui/dpd/__init__.py | 93 + python/gui/logs/.keep | 0 python/gui/run.py | 166 + python/gui/static/css/bootstrap.min.css | 5 + python/gui/static/css/jquery.gritter.css | 100 + python/gui/static/dpd/index.txt | 1 + python/gui/static/fonts/accept.png | Bin 0 -> 1202 bytes python/gui/static/fonts/favicon.ico | Bin 0 -> 318 bytes .../static/fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../static/fonts/glyphicons-halflings-regular.svg | 288 + .../static/fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../static/fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes python/gui/static/fonts/gritter-light.png | Bin 0 -> 4899 bytes python/gui/static/fonts/gritter.png | Bin 0 -> 4880 bytes python/gui/static/fonts/ie-spacer.gif | Bin 0 -> 43 bytes python/gui/static/fonts/warning.png | Bin 0 -> 1418 bytes python/gui/static/js/bootstrap.min.js | 7 + python/gui/static/js/jquery.gritter.js | 408 + python/gui/static/js/jquery.js | 9205 ++++++++++++++++++++ python/gui/static/js/odr-modulator.js | 80 + python/gui/static/js/odr-predistortion.js | 99 + python/gui/static/js/odr-rcvalues.js | 82 + python/gui/static/js/odr.js | 109 + python/gui/templates/about.html | 65 + python/gui/templates/body-nav.html | 39 + python/gui/templates/head.html | 25 + python/gui/templates/home.html | 35 + python/gui/templates/modulator.html | 73 + python/gui/templates/predistortion.html | 88 + python/gui/templates/rcvalues.html | 48 + python/gui/ui-config.json | 9 + python/gui/zmqrc.py | 84 + 141 files changed, 16082 insertions(+), 16078 deletions(-) delete mode 100644 dpd/README.md delete mode 100755 dpd/apply_adapt_dumps.py delete mode 100644 dpd/dpd.ini delete mode 100644 dpd/img/setup_diagram.svg delete mode 100644 dpd/img/setup_photo.svg delete mode 100644 dpd/img/shoulder_measurement_after.png delete mode 100644 dpd/img/shoulder_measurement_before.png delete mode 100755 dpd/iq_file_server.py delete mode 100644 dpd/lut.coef delete mode 100755 dpd/main.py delete mode 100644 dpd/poly.coef delete mode 100755 dpd/show_spectrum.py delete mode 100644 dpd/src/Adapt.py delete mode 100644 dpd/src/Dab_Util.py delete mode 100644 dpd/src/ExtractStatistic.py delete mode 100644 dpd/src/GlobalConfig.py delete mode 100644 dpd/src/Heuristics.py delete mode 100644 dpd/src/MER.py delete mode 100644 dpd/src/Measure.py delete mode 100644 dpd/src/Measure_Shoulders.py delete mode 100644 dpd/src/Model.py delete mode 100644 dpd/src/Model_AM.py delete mode 100644 dpd/src/Model_Lut.py delete mode 100644 dpd/src/Model_PM.py delete mode 100644 dpd/src/Model_Poly.py delete mode 100644 dpd/src/RX_Agc.py delete mode 100644 dpd/src/Symbol_align.py delete mode 100644 dpd/src/TX_Agc.py delete mode 100644 dpd/src/__init__.py delete mode 100644 dpd/src/phase_align.py delete mode 100755 dpd/src/subsample_align.py delete mode 100755 dpd/store_received.py delete mode 100644 gui/README.md delete mode 100755 gui/api/__init__.py delete mode 100644 gui/configuration.py delete mode 100644 gui/dpd/Align.py delete mode 100644 gui/dpd/Capture.py delete mode 100644 gui/dpd/__init__.py delete mode 100644 gui/logs/.keep delete mode 100755 gui/run.py delete mode 100644 gui/static/css/bootstrap.min.css delete mode 100755 gui/static/css/jquery.gritter.css delete mode 100644 gui/static/dpd/index.txt delete mode 100644 gui/static/fonts/accept.png delete mode 100644 gui/static/fonts/favicon.ico delete mode 100644 gui/static/fonts/glyphicons-halflings-regular.eot delete mode 100644 gui/static/fonts/glyphicons-halflings-regular.svg delete mode 100644 gui/static/fonts/glyphicons-halflings-regular.ttf delete mode 100644 gui/static/fonts/glyphicons-halflings-regular.woff delete mode 100644 gui/static/fonts/glyphicons-halflings-regular.woff2 delete mode 100644 gui/static/fonts/gritter-light.png delete mode 100644 gui/static/fonts/gritter.png delete mode 100644 gui/static/fonts/ie-spacer.gif delete mode 100644 gui/static/fonts/warning.png delete mode 100644 gui/static/js/bootstrap.min.js delete mode 100644 gui/static/js/jquery.gritter.js delete mode 100644 gui/static/js/jquery.js delete mode 100644 gui/static/js/odr-modulator.js delete mode 100644 gui/static/js/odr-predistortion.js delete mode 100644 gui/static/js/odr-rcvalues.js delete mode 100644 gui/static/js/odr.js delete mode 100644 gui/templates/about.html delete mode 100644 gui/templates/body-nav.html delete mode 100644 gui/templates/head.html delete mode 100644 gui/templates/home.html delete mode 100644 gui/templates/modulator.html delete mode 100644 gui/templates/predistortion.html delete mode 100644 gui/templates/rcvalues.html delete mode 100644 gui/ui-config.json delete mode 100644 gui/zmqrc.py create mode 100644 python/dpd/README.md create mode 100755 python/dpd/apply_adapt_dumps.py create mode 100644 python/dpd/dpd.ini create mode 100644 python/dpd/img/setup_diagram.svg create mode 100644 python/dpd/img/setup_photo.svg create mode 100644 python/dpd/img/shoulder_measurement_after.png create mode 100644 python/dpd/img/shoulder_measurement_before.png create mode 100755 python/dpd/iq_file_server.py create mode 100644 python/dpd/lut.coef create mode 100755 python/dpd/main.py create mode 100644 python/dpd/poly.coef create mode 100755 python/dpd/show_spectrum.py create mode 100644 python/dpd/src/Adapt.py create mode 100644 python/dpd/src/Dab_Util.py create mode 100644 python/dpd/src/ExtractStatistic.py create mode 100644 python/dpd/src/GlobalConfig.py create mode 100644 python/dpd/src/Heuristics.py create mode 100644 python/dpd/src/MER.py create mode 100644 python/dpd/src/Measure.py create mode 100644 python/dpd/src/Measure_Shoulders.py create mode 100644 python/dpd/src/Model.py create mode 100644 python/dpd/src/Model_AM.py create mode 100644 python/dpd/src/Model_Lut.py create mode 100644 python/dpd/src/Model_PM.py create mode 100644 python/dpd/src/Model_Poly.py create mode 100644 python/dpd/src/RX_Agc.py create mode 100644 python/dpd/src/Symbol_align.py create mode 100644 python/dpd/src/TX_Agc.py create mode 100644 python/dpd/src/__init__.py create mode 100644 python/dpd/src/phase_align.py create mode 100755 python/dpd/src/subsample_align.py create mode 100755 python/dpd/store_received.py create mode 100644 python/gui/README.md create mode 100755 python/gui/api/__init__.py create mode 100644 python/gui/configuration.py create mode 100644 python/gui/dpd/Align.py create mode 100644 python/gui/dpd/Capture.py create mode 100644 python/gui/dpd/__init__.py create mode 100644 python/gui/logs/.keep create mode 100755 python/gui/run.py create mode 100644 python/gui/static/css/bootstrap.min.css create mode 100755 python/gui/static/css/jquery.gritter.css create mode 100644 python/gui/static/dpd/index.txt create mode 100644 python/gui/static/fonts/accept.png create mode 100644 python/gui/static/fonts/favicon.ico create mode 100644 python/gui/static/fonts/glyphicons-halflings-regular.eot create mode 100644 python/gui/static/fonts/glyphicons-halflings-regular.svg create mode 100644 python/gui/static/fonts/glyphicons-halflings-regular.ttf create mode 100644 python/gui/static/fonts/glyphicons-halflings-regular.woff create mode 100644 python/gui/static/fonts/glyphicons-halflings-regular.woff2 create mode 100644 python/gui/static/fonts/gritter-light.png create mode 100644 python/gui/static/fonts/gritter.png create mode 100644 python/gui/static/fonts/ie-spacer.gif create mode 100644 python/gui/static/fonts/warning.png create mode 100644 python/gui/static/js/bootstrap.min.js create mode 100644 python/gui/static/js/jquery.gritter.js create mode 100644 python/gui/static/js/jquery.js create mode 100644 python/gui/static/js/odr-modulator.js create mode 100644 python/gui/static/js/odr-predistortion.js create mode 100644 python/gui/static/js/odr-rcvalues.js create mode 100644 python/gui/static/js/odr.js create mode 100644 python/gui/templates/about.html create mode 100644 python/gui/templates/body-nav.html create mode 100644 python/gui/templates/head.html create mode 100644 python/gui/templates/home.html create mode 100644 python/gui/templates/modulator.html create mode 100644 python/gui/templates/predistortion.html create mode 100644 python/gui/templates/rcvalues.html create mode 100644 python/gui/ui-config.json create mode 100644 python/gui/zmqrc.py diff --git a/README.md b/README.md index 272ba4d..52c01d0 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ Features See doc/README-RC.md for more information - ZeroMQ PUB and REP output. - Ongoing work about digital predistortion for PA linearisation. - See dpd/README.md + See python/dpd/README.md +- Ongoign work for a web GUI. See python/gui/README.md - A prototype algorithm for crest factor reduction. The src/ directory contains the source code of ODR-DabMod. @@ -45,7 +46,8 @@ configuration file and a script for munin integration. The lib/ directory contains source code of libraries needed to build ODR-DabMod. -The dpd/ directory contains the digital predistortion project. It's goal is to reduce distortion that is caused by the non-linearity of the PA. +The python/ directory contains a web-based graphical control interface and +the digital predistortion project. INSTALL ======= diff --git a/dpd/README.md b/dpd/README.md deleted file mode 100644 index 307a2f5..0000000 --- a/dpd/README.md +++ /dev/null @@ -1,264 +0,0 @@ -Digital Predistortion Computation Engine for ODR-DabMod -======================================================= - -This folder contains a digital predistortion prototype. -It was only tested in a laboratory system, and is not ready -for production usage. - -Concept -------- - -ODR-DabMod makes outgoing TX samples and feedback RX samples available to an -external tool. This external tool can request a buffer of samples for analysis, -can calculate coefficients for the predistorter in ODR-DabMod and load the new -coefficients using the remote control. - -The external tool is called the Digital Predistortion Computation Engine (DPDCE). -The DPDCE is written in python, and makes use of the numpy library for -efficient computation. Its sources reside in the *dpd* folder. - -The predistorter in ODR-DabMod supports two modes: polynomial and lookup table. -In the DPDCE, only the polynomial model is implemented at the moment. - -The *dpd/main.py* script is the entry point for the *DPD Computation Engine* -into which these features will be implemented. The tool uses modules from the -*dpd/src/* folder: - -- Sample transfer and time alignment with subsample accuracy is done by *Measure.py* -- Estimating the effects of the PA using some model and calculation of the updated - polynomial coefficients is done in *Model.py* and other specific *Model_XXX.py* files -- Finally, *Adapt.py* updates the ODR-DabMod predistortion setting and digital gain - -These modules themselves use additional helper scripts in the *dpd/src/* folder. - -Requirements ------------- - -- USRP B200. -- Power amplifier. -- A feedback connection from the power amplifier output, such that the average power level at - the USRP RX port is at -45dBm or lower. - Usually this is done with a directional coupler and additional attenuators. -- ODR-DabMod with enabled *dpd_port*, and with a samplerate of 8192000 samples per second. -- Synchronous=1 so that the USRP has the timestamping set properly, internal refclk and pps - are sufficient (not GPSDO necessary). -- A live mux source with TIST enabled. - -See dpd/dpd.ini for an example. - -The DPD server port can be tested with the *dpd/show_spectrum.py* helper tool, which can also display -a constellation diagram. - -Hardware Setup --------------- - -![setup diagram](img/setup_diagram.svg) -![setup photo](img/setup_photo.svg) - -Our setup is depicted in the Figure above. We used components with the following properties: - 1. USRP TX (max +20dBm) - 2. Band III Filter (Mini-Circuits RBP-220W+, 190-250MHz, -3.5dB) - 3. Power amplifier (Mini-Circuits, max +15dBm output, +10 dB gain at 200MHz) - 4. Directional coupler (approx. -25dB @ 223MHz) - 5. Attenuator (-20 dB) - 6. Attenuator (-30 dB) - 7. USRP RX (max -15dBm input allowed, max -45dBm desired) - 8. Spectrum analyzer (max +30dBm allowed) - -It is important to make sure that the USRP RX port does not receive too much -power. Otherwise the USRP will break. Here is an example of how we calculated -the maximal USRP RX input power for our case. As this is only a rough -calculation to protect the port, the predistortion software will later -automatically apply a normalization for the RX input by adapting the USRP RX -gain. - - TX Power + PA Gain - Coupling Factor - Attenuation = 20dBm + 10dB -25dB -50dB = -45dBm - -Thus we have a margin of about 30dB for the input power of the USRP RX port. -Keep in mind we need to calculate using peak power, not average power, and it is -essential that there is no nonlinearity in the RX path! - -Software Setup --------------- - -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 ----------------------- - -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-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 - -The TX gain should be chosen so that you can drive your amplifier into -saturation with a digital gain of 0.1, so that there is margin for the DPD to -operate. - -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_`. 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 -coefficients. Moreover the nuber of measurements increases in each iteration. -You find more information about that in *Heuristic.py*. - -Each plot is stored to the logging directory under a filename containing its -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 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 -difference has increased on your spectrum analyzer, similar to the figure below. - -Without digital predistortion: - -![shoulder_measurement_before](img/shoulder_measurement_before.png) - -With digital predistortion, computed by the DPDCE: - -![shoulder_measurement_after](img/shoulder_measurement_after.png) - -Now see what happens if you apply the predistortions for different TX gains. -You can either set the TX gain before you start the predistortion or using the -command line option `--txgain gain`. You can also try to adjust other -parameters. To see their documentation run `python main.py --help`. - -File format for coefficients ----------------------------- -The coef file contains the polynomial coefficients used in the predistorter. -The file format is very similar to the filtertaps file used in the FIR filter. -It is a text-based format that can easily be inspected and edited in a text -editor. - -The first line contains an integer that defines the predistorter to be used: -1 for polynomial, 2 for lookup table. - -For the polynomial, the subsequent line contains the number of coefficients -as an integer. The second and third lines contain the real, respectively the -imaginary parts of the first coefficient. Fourth and fifth lines give the -second coefficient, and so on. The file therefore contains 1 + 1 + 2xN lines if -it contains N coefficients. - -For the lookup table, the subsequent line contains a float scalefactor that is -applied to the samples in order to bring them into the range of 32-bit unsigned -integer. Then, the next pair of lines contains real and imaginary part of the first -lookup-table entry, which is multiplied to samples in first range. Then it's -followed by 31 other pairs. The entries are complex values close to 1 + 0j. -The file therefore contains 1 + 1 + 2xN lines if it contains N coefficients. - -TODO ----- - - - Understand and fix occasional ODR-DabMod crashes when using DPDCE. - - Make the predistortion more robust. At the moment the shoulders sometimes - increase instead of decrease after applying newly calculated predistortion - parameters. Can this behaviour be predicted from the measurement? This would - make it possible to filter out bad predistortion settings. - - Find a better measurement for the quality of the predistortion. The USRP - might not be good enough to measure large peak-to-shoulder ratios, because - the ADC has 12 bits and DAB signals have a large crest factor. - - Implement a Volterra polynomial to model the PA. Compared to the current - model this would also capture the time dependent behaviour of the PA (memory - effects). - - Continuously observe DAB signal in frequency domain and make sure the power - stays the same. At the moment only the power in the time domain is kept the - 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 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? - -REFERENCES ----------- - -Some papers: - -The paper Raich, Qian, Zhou, "Orthogonal Polynomials for Power Amplifier -Modeling and Predistorter Design" proposes other base polynomials that have -less numerical instability. - -Aladrén, Garcia, Carro, de Mingo, and Sanchez-Perez, "Digital Predistortion -Based on Zernike Polynomial Functions for RF Nonlinear Power Amplifiers". - -Jiang and Wilford, "Digital predistortion for power amplifiers using separable functions" - -Changsoo Eun and Edward J. Powers, "A New Volterra Predistorter Based on the Indirect Learning Architecture" - -Raviv Raich, Hua Qian, and G. Tong Zhou, "Orthogonal Polynomials for Power Amplifier Modeling and Predistorter Design" - - -Models without memory: - -Complex polynomial: y[i] = a1 x[i] + a2 x[i]^2 + a3 x[i]^3 + ... - -The complex polynomial corresponds to the input/output relationship that -applies to the PA in passband (real-valued signal). According to several -sources, this gets transformed to another representation if we consider complex -baseband instead. In the following, all variables are complex. - -Odd-order baseband: y[i] = (b1 + b2 abs(x[i])^2 + b3 abs(x[i])^4) + ...) x[i] - -Complete baseband: y[i] = (b1 + b2 abs(x[i]) + b3 abs(x[i])^2) + ...) x[i] - -with - b_k = 2^{1-k} \binom{k}{(k-1)/2} a_k - - -Models with memory: - - - Hammerstein model: Nonlinearity followed by LTI filter - - Wiener model: LTI filter followed by NL - - Parallel Wiener: input goes to N delays, each delay goes to a NL, all NL outputs summed. - -Taken from slide 36 of [ECE218C Lecture 15](http://www.ece.ucsb.edu/Faculty/rodwell/Classes/ece218c/notes/Lecture15_Digital%20Predistortion_and_Future%20Challenges.pdf) - diff --git a/dpd/apply_adapt_dumps.py b/dpd/apply_adapt_dumps.py deleted file mode 100755 index 20bc013..0000000 --- a/dpd/apply_adapt_dumps.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, apply stored configuration. -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -import datetime -import os -import glob -import logging - -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) - -import src.Adapt as Adapt -import argparse - -parser = argparse.ArgumentParser( - 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('--coefs', default='poly.coef', - help='File with DPD coefficients, which will be read by ODR-DabMod', - required=False) -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 -filename = cli_args.file - -# No need to initialise a GlobalConfig since adapt only needs this one field -class DummyConfig: - def __init__(self): - self.plot_location = None - -c = DummyConfig() - -adapt = Adapt.Adapt(c, port_rc, coef_path) - -print("Loading and applying DPD settings from {}".format(filename)) -adapt.load(filename) - -# 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. diff --git a/dpd/dpd.ini b/dpd/dpd.ini deleted file mode 100644 index 31d6140..0000000 --- a/dpd/dpd.ini +++ /dev/null @@ -1,60 +0,0 @@ -[remotecontrol] -telnet=1 -telnetport=2121 -zmqctrl=1 -zmqctrlendpoint=tcp://127.0.0.1:9400 - -[log] -syslog=0 -filelog=1 -filename=/tmp/dabmod.log - -[input] -transport=tcp -source=localhost:9200 - -[modulator] -gainmode=var -rate=8192000 - -# keep in mind that the DPDCE will set the digital gain through the RC! -digital_gain=0.001 - -[firfilter] -enabled=1 - -[poly] -enabled=1 -polycoeffile=dpd/poly.coef - -# How many threads to use for the predistorter. -# If not set, detect automatically. -#num_threads=2 - -[output] -# to prepare a file for the dpd/iq_file_server.py script, -# use output=file -output=uhd - -[fileoutput] -filename=dpd.iq - -[uhdoutput] -device= -master_clock_rate=32768000 -type=b200 -txgain=80 -channel=13C -refclk_source=internal -pps_source=none -behaviour_refclk_lock_lost=ignore -max_gps_holdover_time=600 -dpd_port=50055 -rxgain=15 - -[delaymanagement] -; Use synchronous=1 so that the USRP time is set. This works -; even in the absence of a reference clk and PPS -synchronous=1 -mutenotimestamps=1 -offset=4.0 diff --git a/dpd/img/setup_diagram.svg b/dpd/img/setup_diagram.svg deleted file mode 100644 index 423ee1e..0000000 --- a/dpd/img/setup_diagram.svg +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - ① USRP TX - - - ① USRP TX - - - - - - - - - - ② Filter - - - ② Filter - - - - - - - - - - ③ PA - - - ③ PA - - - - - - - ④ Dir. Coupler - - - ④ Dir. Coupler - - - - - - - - - - - - - - - - ⑤,⑥ Attenuators - - - ⑤,⑥Attenuators - - - - - - - - ⑦ USRP RX - - - ⑦ USRP RX - - - - - - - - - ⑧ Spectrum Analyzer - - - ⑧ Spectrum Analyzer - - - - diff --git a/dpd/img/setup_photo.svg b/dpd/img/setup_photo.svg deleted file mode 100644 index a401fda..0000000 --- a/dpd/img/setup_photo.svg +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - ⑤⑥ - - - diff --git a/dpd/img/shoulder_measurement_after.png b/dpd/img/shoulder_measurement_after.png deleted file mode 100644 index 2631256..0000000 Binary files a/dpd/img/shoulder_measurement_after.png and /dev/null differ diff --git a/dpd/img/shoulder_measurement_before.png b/dpd/img/shoulder_measurement_before.png deleted file mode 100644 index 3581a72..0000000 Binary files a/dpd/img/shoulder_measurement_before.png and /dev/null differ diff --git a/dpd/iq_file_server.py b/dpd/iq_file_server.py deleted file mode 100755 index 7a4e570..0000000 --- a/dpd/iq_file_server.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# This example server simulates the ODR-DabMod's -# DPD server, taking samples from an IQ file -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -import sys -import socket -import struct -import argparse -import numpy as np -from datetime import datetime - -SIZEOF_SAMPLE = 8 # complex floats -# Constants for TM 1 -NbSymbols = 76 -NbCarriers = 1536 -Spacing = 2048 -NullSize = 2656 -SymSize = 2552 -FicSizeOut = 288 -FrameSize = NullSize + NbSymbols*SymSize - -def main(): - parser = argparse.ArgumentParser(description="Simulate ODR-DabMod DPD server") - parser.add_argument('--port', default='50055', - help='port to listen on (default: 50055)', - required=False) - parser.add_argument('--file', help='I/Q File to read from (complex floats)', - required=True) - parser.add_argument('--samplerate', default='8192000', help='Sample rate', - required=False) - - cli_args = parser.parse_args() - - serve_file(cli_args) - -def recv_exact(sock, num_bytes): - bufs = [] - while num_bytes > 0: - b = sock.recv(num_bytes) - if len(b) == 0: - break - num_bytes -= len(b) - bufs.append(b) - return b''.join(bufs) - -def serve_file(options): - oversampling = int(int(options.samplerate) / 2048000) - consumesamples = 8*FrameSize * oversampling - iq_data = np.fromfile(options.file, count=consumesamples, dtype=np.complex64) - - print("Loaded {} samples of IQ data".format(len(iq_data))) - - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(('localhost', int(options.port))) - s.listen() - - try: - while True: - sock, addr_info = s.accept() - print("Got a connection from {}".format(addr_info)) - - ver = recv_exact(sock, 1) - (num_samps,) = struct.unpack("=I", recv_exact(sock, 4)) - num_bytes = num_samps * SIZEOF_SAMPLE - - if num_bytes > len(iq_data): - print("Truncating length to {} samples".format(len(iq_data))) - num_samps = len(iq_data) - num_bytes = num_samps * 4 - - tx_sec = datetime.now().timestamp() - tx_pps = int(16384000 * (tx_sec - int(tx_sec))) - tx_second = int(tx_sec) - - # TX metadata and data - sock.sendall(struct.pack("=III", num_samps, tx_second, tx_pps)) - sock.sendall(iq_data[-num_samps:].tobytes()) - - # RX metadata and data - rx_second = tx_second + 1 - rx_pps = tx_pps - sock.sendall(struct.pack("=III", num_samps, rx_second, rx_pps)) - sock.sendall(iq_data[-num_samps:].tobytes()) - - print("Sent {} samples".format(num_samps)) - - sock.close() - finally: - s.close() - raise - -main() - - -# The MIT License (MIT) -# -# 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. diff --git a/dpd/lut.coef b/dpd/lut.coef deleted file mode 100644 index a198d56..0000000 --- a/dpd/lut.coef +++ /dev/null @@ -1,64 +0,0 @@ -2 -4294967296 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 -1 -0 diff --git a/dpd/main.py b/dpd/main.py deleted file mode 100755 index 10a56fc..0000000 --- a/dpd/main.py +++ /dev/null @@ -1,336 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# DPD Computation Engine main file. -# -# 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 sys -import datetime -import os -import argparse -import matplotlib - -matplotlib.use('Agg') - -parser = argparse.ArgumentParser( - description="DPD Computation Engine for 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='poly.coef', - help='File with DPD coefficients, which will be read by ODR-DabMod', - required=False) -parser.add_argument('--txgain', default=-1, - help='TX Gain, -1 to leave unchanged', - required=False, - type=int) -parser.add_argument('--rxgain', default=30, - help='TX Gain, -1 to leave unchanged', - required=False, - type=int) -parser.add_argument('--digital_gain', default=0.01, - help='Digital Gain', - required=False, - type=float) -parser.add_argument('--target_median', default=0.05, - help='The target median for the RX and TX AGC', - 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('-i', '--iterations', default=10, type=int, - help='Number of iterations to run', - required=False) -parser.add_argument('-L', '--lut', - help='Use lookup table instead of polynomial predistorter', - action="store_true") -parser.add_argument('--enable-txgain-agc', - help='Enable the TX gain AGC', - action="store_true") -parser.add_argument('--plot', - 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.') -parser.add_argument('--measure', action="store_true", - help='Only measure metrics once') - -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 -num_iter = cli_args.iterations -rxgain = cli_args.rxgain -txgain = cli_args.txgain -name = cli_args.name -plot = cli_args.plot - -# Logging -import logging - -# Simple usage scenarios don't need to clutter /tmp -if not (cli_args.status or cli_args.reset or cli_args.measure): - 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.INFO) - logging_path = None - -logging.info("DPDCE starting up with options: {}".format(cli_args)) - -import numpy as np -import traceback -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, 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.GlobalConfig import GlobalConfig -from src.MER import MER -from src.Measure_Shoulders import Measure_Shoulders - -c = GlobalConfig(cli_args, logging_path) -SA = Symbol_align(c) -MER = MER(c) -MS = Measure_Shoulders(c) -meas = Measure(c, cli_args.samplerate, port, cli_args.samps) -extStat = ExtractStatistic(c) -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( - txgain, 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) - -# Set RX Gain -if rxgain == -1: - rxgain = adapt.get_rxgain() -else: - adapt.set_rxgain(rxgain) - -# Set TX Gain -if txgain == -1: - txgain = adapt.get_txgain() -else: - adapt.set_txgain(txgain) - -tx_gain = adapt.get_txgain() -rx_gain = adapt.get_rxgain() -digital_gain = adapt.get_digital_gain() - -dpddata = adapt.get_predistorter() - -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) - -# Automatic Gain Control -agc = Agc(meas, adapt, c) -agc.run() - -if cli_args.measure: - txframe_aligned, tx_ts, rxframe_aligned, rx_ts, rx_median = meas.get_samples() - - print("TX signal median {}".format(np.median(np.abs(txframe_aligned)))) - print("RX signal median {}".format(rx_median)) - - tx, rx, phase_diff, n_per_bin = extStat.extract(txframe_aligned, rxframe_aligned) - - off = SA.calc_offset(txframe_aligned) - print("off {}".format(off)) - tx_mer = MER.calc_mer(txframe_aligned[off:off + c.T_U], debug_name='TX') - print("tx_mer {}".format(tx_mer)) - rx_mer = MER.calc_mer(rxframe_aligned[off:off + c.T_U], debug_name='RX') - print("rx_mer {}".format(rx_mer)) - - mse = np.mean(np.abs((txframe_aligned - rxframe_aligned) ** 2)) - print("mse {}".format(mse)) - - digital_gain = adapt.get_digital_gain() - print("digital_gain {}".format(digital_gain)) - - #rx_shoulder_tuple = MS.average_shoulders(rxframe_aligned) - #tx_shoulder_tuple = MS.average_shoulders(txframe_aligned) - sys.exit(0) - -# Disable TXGain AGC by default, so that the experiments are controlled -# better. -tx_agc = None -if cli_args.enable_txgain_agc: - tx_agc = TX_Agc(adapt, c) - -state = 'report' -i = 0 -lr = None -n_meas = None -while i < num_iter: - try: - # Measure - if state == 'measure': - # Get Samples and check gain - txframe_aligned, tx_ts, rxframe_aligned, rx_ts, rx_median = meas.get_samples() - if tx_agc and tx_agc.adapt_if_necessary(txframe_aligned): - continue - - # Extract usable data from measurement - tx, rx, phase_diff, n_per_bin = extStat.extract(txframe_aligned, rxframe_aligned) - - n_meas = Heuristics.get_n_meas(i) - if extStat.n_meas >= n_meas: # Use as many measurements nr of runs - state = 'model' - else: - state = 'measure' - - # Model - elif state == 'model': - # Calculate new model parameters and delete old measurements - if any([x is None for x in [tx, rx, phase_diff]]): - logging.error("No data to calculate model") - state = 'measure' - continue - - lr = Heuristics.get_learning_rate(i) - model.train(tx, rx, phase_diff, lr=lr) - dpddata = model.get_dpd_data() - extStat = ExtractStatistic(c) - state = 'adapt' - - # Adapt - elif state == 'adapt': - adapt.set_predistorter(dpddata) - state = 'report' - - # Report - elif state == 'report': - try: - txframe_aligned, tx_ts, rxframe_aligned, rx_ts, rx_median = meas.get_samples() - - # Store all settings for pre-distortion, tx and rx - adapt.dump() - - # Collect logging data - off = SA.calc_offset(txframe_aligned) - tx_mer = MER.calc_mer(txframe_aligned[off:off + c.T_U], debug_name='TX') - rx_mer = MER.calc_mer(rxframe_aligned[off:off + c.T_U], debug_name='RX') - mse = np.mean(np.abs((txframe_aligned - rxframe_aligned) ** 2)) - tx_gain = adapt.get_txgain() - rx_gain = adapt.get_rxgain() - digital_gain = adapt.get_digital_gain() - tx_median = np.median(np.abs(txframe_aligned)) - rx_shoulder_tuple = MS.average_shoulders(rxframe_aligned) - tx_shoulder_tuple = MS.average_shoulders(txframe_aligned) - - # Generic logging - logging.info(list((name, eval(name)) for name in - ['i', 'tx_mer', 'tx_shoulder_tuple', 'rx_mer', - 'rx_shoulder_tuple', 'mse', 'tx_gain', - 'digital_gain', 'rx_gain', 'rx_median', - 'tx_median', 'lr', 'n_meas'])) - - # Model specific logging - if dpddata[0] == 'poly': - coefs_am = dpddata[1] - coefs_pm = dpddata[2] - logging.info('It {}: coefs_am {}'. - format(i, coefs_am)) - logging.info('It {}: coefs_pm {}'. - format(i, coefs_pm)) - elif dpddata[0] == 'lut': - scalefactor = dpddata[1] - lut = dpddata[2] - logging.info('It {}: LUT scalefactor {}, LUT {}'. - format(i, scalefactor, lut)) - except: - logging.error('Iteration {}: Report failed.'.format(i)) - logging.error(traceback.format_exc()) - i += 1 - state = 'measure' - - except: - logging.error('Iteration {} failed.'.format(i)) - logging.error(traceback.format_exc()) - i += 1 - state = 'measure' - -# 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. diff --git a/dpd/poly.coef b/dpd/poly.coef deleted file mode 100644 index 248d316..0000000 --- a/dpd/poly.coef +++ /dev/null @@ -1,12 +0,0 @@ -1 -5 -1.0 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 diff --git a/dpd/show_spectrum.py b/dpd/show_spectrum.py deleted file mode 100755 index f23dba2..0000000 --- a/dpd/show_spectrum.py +++ /dev/null @@ -1,276 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# This is an example tool that shows how to connect to ODR-DabMod's dpd TCP -# server and get samples from there. -# -# Since the TX and RX samples are not perfectly aligned, the tool has to align -# them properly, which is done in two steps: First on sample-level using a -# correlation, then with subsample accuracy using a FFT approach. -# -# It requires SciPy and matplotlib. -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -import sys -import socket -import struct -import numpy as np -import matplotlib.pyplot as pp -from matplotlib.animation import FuncAnimation -import argparse -from scipy.misc import imsave - -SIZEOF_SAMPLE = 8 # complex floats - -# Constants for TM 1 -NbSymbols = 76 -NbCarriers = 1536 -Spacing = 2048 -NullSize = 2656 -SymSize = 2552 -FicSizeOut = 288 - -def main(): - parser = argparse.ArgumentParser(description="Plot the spectrum of ODR-DabMod's DPD feedback") - parser.add_argument('--samps', default='10240', help='Number of samples to request at once', - required=False) - parser.add_argument('--port', default='50055', - help='port to connect to ODR-DabMod DPD (default: 50055)', - required=False) - parser.add_argument('--animated', action='store_true', help='Enable real-time animation') - parser.add_argument('--constellation', action='store_true', help='Draw constellaton plot') - parser.add_argument('--samplerate', default='8192000', help='Sample rate', - required=False) - - cli_args = parser.parse_args() - - if cli_args.constellation: - plot_constellation_once(cli_args) - elif cli_args.animated: - plot_spectrum_animated(cli_args) - else: - plot_spectrum_once(cli_args) - -def recv_exact(sock, num_bytes): - bufs = [] - while num_bytes > 0: - b = sock.recv(num_bytes) - if len(b) == 0: - break - num_bytes -= len(b) - bufs.append(b) - return b''.join(bufs) - -def get_samples(port, num_samps_to_request): - """Connect to ODR-DabMod, retrieve TX and RX samples, load - into numpy arrays, and return a tuple - (tx_timestamp, tx_samples, rx_timestamp, rx_samples) - where the timestamps are doubles, and the samples are numpy - arrays of complex floats, both having the same size - """ - - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect(('localhost', port)) - - print("Send version"); - s.sendall(b"\x01") - - print("Send request for {} samples".format(num_samps_to_request)) - s.sendall(struct.pack("=I", num_samps_to_request)) - - print("Wait for TX metadata") - num_samps, tx_second, tx_pps = struct.unpack("=III", recv_exact(s, 12)) - tx_ts = tx_second + tx_pps / 16384000.0 - - if num_samps > 0: - print("Receiving {} TX samples".format(num_samps)) - txframe_bytes = recv_exact(s, num_samps * SIZEOF_SAMPLE) - txframe = np.fromstring(txframe_bytes, dtype=np.complex64) - else: - txframe = np.array([], dtype=np.complex64) - - - print("Wait for RX metadata") - rx_second, rx_pps = struct.unpack("=II", recv_exact(s, 8)) - rx_ts = rx_second + rx_pps / 16384000.0 - - if num_samps > 0: - print("Receiving {} RX samples".format(num_samps)) - rxframe_bytes = recv_exact(s, num_samps * SIZEOF_SAMPLE) - rxframe = np.fromstring(rxframe_bytes, dtype=np.complex64) - else: - rxframe = np.array([], dtype=np.complex64) - - print("Disconnecting") - s.close() - - return (tx_ts, txframe, rx_ts, rxframe) - -def recv_rxtx(port, num_samps_to_request): - tx_ts, txframe, rx_ts, rxframe = get_samples(port, num_samps_to_request) - - # convert to complex doubles for more dynamic range - txframe = txframe.astype(np.complex128) - rxframe = rxframe.astype(np.complex128) - - print("Received {} & {} frames at {} and {}".format( - len(txframe), len(rxframe), tx_ts, rx_ts)) - return tx_ts, txframe, rx_ts, rxframe - -def get_spectrum(port, num_samps_to_request): - tx_ts, txframe, rx_ts, rxframe = recv_rxtx(port, num_samps_to_request) - - print("Calculate TX and RX spectrum assuming 8192000 samples per second") - tx_spectrum = np.fft.fftshift(np.fft.fft(txframe, fft_size)) - tx_power = 20*np.log10(np.abs(tx_spectrum)) - - rx_spectrum = np.fft.fftshift(np.fft.fft(rxframe, fft_size)) - rx_power = 20*np.log10(np.abs(rx_spectrum)) - return tx_power, rx_power - -def remove_guard_intervals(frame, options): - """Remove the cyclic prefix. The frame needs to be aligned to the - end of the transmission frame. Transmission Mode 1 is assumed""" - oversample = int(int(options.samplerate) / 2048000) - - # From the end, take 2048 samples, then skip 504 samples - frame = frame[::-1] - - stride_len = Spacing * oversample - stride_advance = SymSize * oversample - - # Truncate the frame to an integer length of strides - newlen = len(frame) - (len(frame) % stride_advance) - print("Truncating frame from {} to {}".format(len(frame), newlen)) - frame = frame[:newlen] - - # Remove the cyclic prefix - frame = frame.reshape(-1, stride_advance)[:,:stride_len].reshape(-1) - - # Reverse again - return frame[::-1] - - -def plot_constellation_once(options): - port = int(options.port) - num_samps_to_request = int(options.samps) - - tx_ts, txframe, rx_ts, rxframe = recv_rxtx(port, num_samps_to_request) - - frame = remove_guard_intervals(txframe, options) - - oversample = int(int(options.samplerate) / 2048000) - - n = Spacing * oversample # is also number of samples per symbol - if len(frame) % n != 0: - raise ValueError("Frame length doesn't contain exact number of symbols") - num_syms = int(len(frame) / n) - print("frame {} has {} symbols".format(len(frame), num_syms)) - spectrums = np.array([np.fft.fftshift(np.fft.fft(frame[n*i:n*(i+1)], n)) for i in range(num_syms)]) - - def normalise(x): - """Normalise a real-valued array x to the range [0,1]""" - y = x + np.min(x) - return x / np.max(x) - - imsave("spectrums.png", np.concatenate([ - normalise(np.abs(spectrums)), - normalise(np.angle(spectrums))])) - - # Only take bins that are supposed to contain energy - # i.e. the middle 1536 bins, excluding the bin at n/2 - assert(n % 2 == 0) - n_half = int(n/2) - spectrums = np.concatenate( - [spectrums[...,n_half-768:n_half], - spectrums[...,n_half + 1:n_half + 769]], axis=1) - - sym_indices = (np.tile(np.arange(num_syms-1).reshape(num_syms-1,1), (1,NbCarriers)) + - np.tile(np.linspace(-0.4, 0.4, NbCarriers), (num_syms-1, 1) ) ) - sym_indices = sym_indices.reshape(-1) - diff_angles = np.mod(np.diff(np.angle(spectrums, deg=1), axis=0), 360) - #sym_points = spectrums[:-1].reshape(-1) - # Set amplitude and phase of low power points to zero, avoid cluttering diagram - #sym_points[np.abs(sym_points) < np.mean(np.abs(sym_points)) * 0.1] = 0 - - print("ix {} spec {} da {}".format( - sym_indices.shape, spectrums.shape, diff_angles.shape)) - - fig = pp.figure() - - fig.suptitle("Constellation") - ax1 = fig.add_subplot(111) - ax1.set_title("TX") - ax1.scatter(sym_indices, diff_angles.reshape(-1), alpha=0.1) - - pp.show() - -fft_size = 4096 - -def plot_spectrum_once(options): - port = int(options.port) - num_samps_to_request = int(options.samps) - freqs = np.fft.fftshift(np.fft.fftfreq(fft_size, d=1./int(options.samplerate))) - - tx_power, rx_power = get_spectrum(port, num_samps_to_request) - fig = pp.figure() - - fig.suptitle("TX and RX spectrum") - ax1 = fig.add_subplot(211) - ax1.set_title("TX") - ax1.plot(freqs, tx_power, 'r') - ax2 = fig.add_subplot(212) - ax2.set_title("RX") - ax2.plot(freqs, rx_power, 'b') - pp.show() - -def plot_spectrum_animated(options): - port = int(options.port) - num_samps_to_request = int(options.samps) - freqs = np.fft.fftshift(np.fft.fftfreq(fft_size, d=1./int(options.samplerate))) - - fig, axes = pp.subplots(2, sharex=True) - line1, = axes[0].plot(freqs, np.ones(len(freqs)), 'r', animated=True) - axes[0].set_title("TX") - line2, = axes[1].plot(freqs, np.ones(len(freqs)), 'b', animated=True) - axes[1].set_title("RX") - lines = [line1, line2] - - axes[0].set_ylim(-30, 50) - axes[1].set_ylim(-60, 40) - - def update(frame): - tx_power, rx_power = get_spectrum(port, num_samps_to_request) - - lines[0].set_ydata(tx_power) - lines[1].set_ydata(rx_power) - return lines - - ani = FuncAnimation(fig, update, blit=True) - pp.show() - -main() - -# The MIT License (MIT) -# -# 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. diff --git a/dpd/src/Adapt.py b/dpd/src/Adapt.py deleted file mode 100644 index a57602f..0000000 --- a/dpd/src/Adapt.py +++ /dev/null @@ -1,286 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine: updates ODR-DabMod's settings -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file -""" -This module is used to change settings of ODR-DabMod using -the ZMQ remote control socket. -""" - -import zmq -import logging -import numpy as np -import os -import datetime -import pickle - -LUT_LEN = 32 -FORMAT_POLY = 1 -FORMAT_LUT = 2 - - -def _write_poly_coef_file(coefs_am, coefs_pm, path): - assert (len(coefs_am) == len(coefs_pm)) - - f = open(path, 'w') - f.write("{}\n{}\n".format(FORMAT_POLY, len(coefs_am))) - for coef in coefs_am: - f.write("{}\n".format(coef)) - for coef in coefs_pm: - f.write("{}\n".format(coef)) - f.close() - - -def _write_lut_file(scalefactor, lut, path): - assert (len(lut) == LUT_LEN) - - f = open(path, 'w') - f.write("{}\n{}\n".format(FORMAT_LUT, scalefactor)) - for coef in lut: - 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 - - Parameters - ---------- - port : int - Port at which the ODR-DabMod is listening to connect the - ZMQ remote control. - """ - - 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" - self._context = zmq.Context() - - def _connect(self): - """Establish the connection to ODR-DabMod using - a ZMQ socket that is in request mode (Client). - Returns a socket""" - sock = self._context.socket(zmq.REQ) - poller = zmq.Poller() - poller.register(sock, zmq.POLLIN) - - sock.connect("tcp://%s:%d" % (self.host, self.port)) - - sock.send(b"ping") - - socks = dict(poller.poll(1000)) - if socks: - if socks.get(sock) == zmq.POLLIN: - data = [el.decode() for el in sock.recv_multipart()] - - if data != ['ok']: - raise RuntimeError( - "Invalid ZMQ RC answer to 'ping' at %s %d: %s" % - (self.host, self.port, data)) - else: - sock.close(linger=10) - raise RuntimeError( - "ZMQ RC does not respond to 'ping' at %s %d" % - (self.host, self.port)) - - return sock - - def send_receive(self, message): - """Send a message to ODR-DabMod. It always - returns the answer ODR-DabMod sends back. - - An example message could be - "get sdr txgain" or "set sdr txgain 50" - - Parameter - --------- - message : str - The message string that will be sent to the receiver. - """ - sock = self._connect() - logging.debug("Send message: %s" % message) - msg_parts = message.split(" ") - for i, part in enumerate(msg_parts): - if i == len(msg_parts) - 1: - f = 0 - else: - f = zmq.SNDMORE - - sock.send(part.encode(), flags=f) - - data = [el.decode() for el in sock.recv_multipart()] - logging.debug("Received message: %s" % message) - return data - - def set_txgain(self, gain): - """Set a new txgain for the ODR-DabMod. - - Parameters - ---------- - gain : int - new TX gain, in the same format as ODR-DabMod's config file - """ - # TODO this is specific to the B200 - if gain < 0 or gain > 89: - raise ValueError("Gain has to be in [0,89]") - return self.send_receive("set sdr txgain %.4f" % float(gain)) - - def get_txgain(self): - """Get the txgain value in dB for the ODR-DabMod.""" - # TODO handle failure - return float(self.send_receive("get sdr txgain")[0]) - - def set_rxgain(self, gain): - """Set a new rxgain for the ODR-DabMod. - - Parameters - ---------- - gain : int - new RX gain, in the same format as ODR-DabMod's config file - """ - # TODO this is specific to the B200 - if gain < 0 or gain > 89: - raise ValueError("Gain has to be in [0,89]") - return self.send_receive("set sdr rxgain %.4f" % float(gain)) - - def get_rxgain(self): - """Get the rxgain value in dB for the ODR-DabMod.""" - # TODO handle failure - return float(self.send_receive("get sdr rxgain")[0]) - - def set_digital_gain(self, gain): - """Set a new rxgain for the ODR-DabMod. - - Parameters - ---------- - gain : int - new RX gain, in the same format as ODR-DabMod's config file - """ - msg = "set gain digital %.5f" % gain - return self.send_receive(msg) - - def get_digital_gain(self): - """Get the rxgain value in dB for the ODR-DabMod.""" - # TODO handle failure - return float(self.send_receive("get gain digital")[0]) - - def get_predistorter(self): - """Load the coefficients from the file in the format given in the README, - return ("poly", [AM coef], [PM coef]) or ("lut", scalefactor, [LUT entries]) - """ - f = open(self.coef_path, 'r') - lines = f.readlines() - predistorter_format = int(lines[0]) - if predistorter_format == FORMAT_POLY: - coefs_am_out = [] - coefs_pm_out = [] - n_coefs = int(lines[1]) - coefs = [float(l) for l in lines[2:]] - i = 0 - for c in coefs: - if i < n_coefs: - coefs_am_out.append(c) - elif i < 2 * n_coefs: - coefs_pm_out.append(c) - else: - raise ValueError( - 'Incorrect coef file format: too many' - ' coefficients in {}, should be {}, coefs are {}' - .format(self.coef_path, n_coefs, coefs)) - i += 1 - f.close() - return 'poly', coefs_am_out, coefs_pm_out - elif predistorter_format == FORMAT_LUT: - scalefactor = int(lines[1]) - coefs = np.array([float(l) for l in lines[2:]], dtype=np.float32) - coefs = coefs.reshape((-1, 2)) - lut = coefs[..., 0] + 1j * coefs[..., 1] - if len(lut) != LUT_LEN: - raise ValueError("Incorrect number of LUT entries ({} expected {})".format(len(lut), LUT_LEN)) - return 'lut', scalefactor, lut - else: - raise ValueError("Unknown predistorter format {}".format(predistorter_format)) - - def set_predistorter(self, dpddata): - """Update the predistorter data in the modulator. Takes the same - tuple format as argument than the one returned get_predistorter()""" - if dpddata[0] == "poly": - coefs_am = dpddata[1] - coefs_pm = dpddata[2] - _write_poly_coef_file(coefs_am, coefs_pm, self.coef_path) - elif dpddata[0] == "lut": - scalefactor = dpddata[1] - lut = dpddata[2] - _write_lut_file(scalefactor, lut, self.coef_path) - else: - raise ValueError("Unknown predistorter '{}'".format(dpddata[0])) - self.send_receive("set memlesspoly coeffile {}".format(self.coef_path)) - - def dump(self, path=None): - """Backup current settings to a file""" - dt = datetime.datetime.now().isoformat() - if path is None: - 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, "wb") as f: - pickle.dump(d, f) - - return path - - def load(self, path): - """Restore settings from a file""" - 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"]) - -# The MIT License (MIT) -# -# Copyright (c) 2017 Andreas Steger, 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. diff --git a/dpd/src/Dab_Util.py b/dpd/src/Dab_Util.py deleted file mode 100644 index bc89a39..0000000 --- a/dpd/src/Dab_Util.py +++ /dev/null @@ -1,246 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, utilities for working with DAB signals. -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -import datetime -import os -import logging -import numpy as np -import matplotlib - -matplotlib.use('agg') -import matplotlib.pyplot as plt -import src.subsample_align as sa -import src.phase_align as pa -from scipy import signal - - -def fromfile(filename, offset=0, length=None): - if length is None: - return np.memmap(filename, dtype=np.complex64, mode='r', offset=64 / 8 * offset) - else: - return np.memmap(filename, dtype=np.complex64, mode='r', offset=64 / 8 * offset, shape=length) - - -class Dab_Util: - """Collection of methods that can be applied to an array - complex IQ samples of a DAB signal - """ - - 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 - - self.plot = plot - - def lag(self, sig_orig, sig_rec): - """ - Find lag between two signals - Args: - sig_orig: The signal that has been sent - sig_rec: The signal that has been recored - """ - off = sig_rec.shape[0] - c = np.abs(signal.correlate(sig_orig, sig_rec)) - - if self.plot and self.c.plot_location is not None: - dt = datetime.datetime.now().isoformat() - corr_path = self.c.plot_location + "/" + dt + "_tx_rx_corr.png" - plt.plot(c, label="corr") - plt.legend() - plt.savefig(corr_path) - plt.close() - - return np.argmax(c) - off + 1 - - def lag_upsampling(self, sig_orig, sig_rec, n_up): - if n_up != 1: - sig_orig_up = signal.resample(sig_orig, sig_orig.shape[0] * n_up) - sig_rec_up = signal.resample(sig_rec, sig_rec.shape[0] * n_up) - else: - sig_orig_up = sig_orig - sig_rec_up = sig_rec - l = self.lag(sig_orig_up, sig_rec_up) - l_orig = float(l) / n_up - return l_orig - - def subsample_align_upsampling(self, sig_tx, sig_rx, n_up=32): - """ - Returns an aligned version of sig_tx and sig_rx by cropping and subsample alignment - Using upsampling - """ - assert (sig_tx.shape[0] == sig_rx.shape[0]) - - if sig_tx.shape[0] % 2 == 1: - sig_tx = sig_tx[:-1] - sig_rx = sig_rx[:-1] - - sig1_up = signal.resample(sig_tx, sig_tx.shape[0] * n_up) - sig2_up = signal.resample(sig_rx, sig_rx.shape[0] * n_up) - - off_meas = self.lag_upsampling(sig2_up, sig1_up, n_up=1) - off = int(abs(off_meas)) - - if off_meas > 0: - sig1_up = sig1_up[:-off] - sig2_up = sig2_up[off:] - elif off_meas < 0: - sig1_up = sig1_up[off:] - sig2_up = sig2_up[:-off] - - sig_tx = signal.resample(sig1_up, sig1_up.shape[0] / n_up).astype(np.complex64) - sig_rx = signal.resample(sig2_up, sig2_up.shape[0] / n_up).astype(np.complex64) - return sig_tx, sig_rx - - def subsample_align(self, sig_tx, sig_rx): - """ - Returns an aligned version of sig_tx and sig_rx by cropping and subsample alignment - """ - - if self.plot and self.c.plot_location is not None: - dt = datetime.datetime.now().isoformat() - 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") - axs[0].plot(np.abs(sig_rx[:128]), label="RX Frame") - axs[0].set_title("Raw Data") - axs[0].set_ylabel("Amplitude") - axs[0].set_xlabel("Samples") - axs[0].legend(loc=4) - - axs[1].plot(np.real(sig_tx[:128]), label="TX Frame") - axs[1].plot(np.real(sig_rx[:128]), label="RX Frame") - axs[1].set_title("Raw Data") - axs[1].set_ylabel("Real Part") - axs[1].set_xlabel("Samples") - axs[1].legend(loc=4) - - fig.tight_layout() - fig.savefig(fig_path) - plt.close(fig) - - off_meas = self.lag_upsampling(sig_rx, sig_tx, n_up=1) - off = int(abs(off_meas)) - - logging.debug("sig_tx_orig: {} {}, sig_rx_orig: {} {}, offset {}".format( - len(sig_tx), - sig_tx.dtype, - len(sig_rx), - sig_rx.dtype, - off_meas)) - - if off_meas > 0: - sig_tx = sig_tx[:-off] - sig_rx = sig_rx[off:] - elif off_meas < 0: - sig_tx = sig_tx[off:] - sig_rx = sig_rx[:-off] - - if off % 2 == 1: - sig_tx = sig_tx[:-1] - sig_rx = sig_rx[:-1] - - if self.plot and self.c.plot_location is not None: - dt = datetime.datetime.now().isoformat() - 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") - axs[0].plot(np.abs(sig_rx[:128]), label="RX Frame") - axs[0].set_title("Sample Aligned Data") - axs[0].set_ylabel("Amplitude") - axs[0].set_xlabel("Samples") - axs[0].legend(loc=4) - - axs[1].plot(np.real(sig_tx[:128]), label="TX Frame") - axs[1].plot(np.real(sig_rx[:128]), label="RX Frame") - axs[1].set_ylabel("Real Part") - axs[1].set_xlabel("Samples") - axs[1].legend(loc=4) - - fig.tight_layout() - fig.savefig(fig_path) - plt.close(fig) - - sig_rx = sa.subsample_align(sig_rx, sig_tx) - - if self.plot and self.c.plot_location is not None: - dt = datetime.datetime.now().isoformat() - 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") - axs[0].plot(np.abs(sig_rx[:128]), label="RX Frame") - axs[0].set_title("Subsample Aligned") - axs[0].set_ylabel("Amplitude") - axs[0].set_xlabel("Samples") - axs[0].legend(loc=4) - - axs[1].plot(np.real(sig_tx[:128]), label="TX Frame") - axs[1].plot(np.real(sig_rx[:128]), label="RX Frame") - axs[1].set_ylabel("Real Part") - axs[1].set_xlabel("Samples") - axs[1].legend(loc=4) - - fig.tight_layout() - fig.savefig(fig_path) - plt.close(fig) - - sig_rx = pa.phase_align(sig_rx, sig_tx) - - if self.plot and self.c.plot_location is not None: - dt = datetime.datetime.now().isoformat() - 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") - axs[0].plot(np.abs(sig_rx[:128]), label="RX Frame") - axs[0].set_title("Phase Aligned") - axs[0].set_ylabel("Amplitude") - axs[0].set_xlabel("Samples") - axs[0].legend(loc=4) - - axs[1].plot(np.real(sig_tx[:128]), label="TX Frame") - axs[1].plot(np.real(sig_rx[:128]), label="RX Frame") - axs[1].set_ylabel("Real Part") - axs[1].set_xlabel("Samples") - axs[1].legend(loc=4) - - fig.tight_layout() - fig.savefig(fig_path) - 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 - -# 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/ExtractStatistic.py b/dpd/src/ExtractStatistic.py deleted file mode 100644 index 639513a..0000000 --- a/dpd/src/ExtractStatistic.py +++ /dev/null @@ -1,196 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, -# Extract statistic from received TX and RX 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 matplotlib.pyplot as plt -import datetime -import os -import logging - - -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" - - -def _phase_diff_value_per_bin(phase_diffs_values_lists): - phase_list = [] - for values in phase_diffs_values_lists: - mean = np.mean(values) if len(values) > 0 else np.nan - phase_list.append(mean) - return phase_list - - -class ExtractStatistic: - """Calculate a low variance RX value for equally spaced tx values - of a predefined range""" - - def __init__(self, c): - self.c = c - - # Number of measurements used to extract the statistic - self.n_meas = 0 - - # Boundaries for the bins - 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 - - # List of rx values for each bin - self.rx_values_lists = [] - for i in range(c.ES_n_bins): - self.rx_values_lists.append([]) - - # List of tx values for each bin - self.tx_values_lists = [] - for i in range(c.ES_n_bins): - self.tx_values_lists.append([]) - - self.plot = c.ES_plot - - def _plot_and_log(self, tx_values, rx_values, phase_diffs_values, phase_diffs_values_lists): - if self.plot and self.c.plot_location is not None: - dt = datetime.datetime.now().isoformat() - 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)) - i_sub = 0 - - i_sub += 1 - ax = plt.subplot(sub_rows, sub_cols, i_sub) - ax.plot(tx_values, rx_values, - label="Estimated Values", - color="red") - for i, tx_value in enumerate(tx_values): - rx_values_list = self.rx_values_lists[i] - ax.scatter(np.ones(len(rx_values_list)) * tx_value, - np.abs(rx_values_list), - 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(tx_values, np.rad2deg(phase_diffs_values), - label="Estimated Values", - color="red") - for i, tx_value in enumerate(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(tx_values): - rx_values_list = self.rx_values_lists[i] - num.append(len(rx_values_list)) - 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) - - def _rx_value_per_bin(self): - rx_values = [] - for values in self.rx_values_lists: - mean = np.mean(np.abs(values)) if len(values) > 0 else np.nan - rx_values.append(mean) - 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 extract(self, tx_dpd, rx): - """Extract information from a new measurement and store them - in member variables.""" - _check_input_extract(tx_dpd, rx) - self.n_meas += 1 - - 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]) - - rx_values = self._rx_value_per_bin() - tx_values = self._tx_value_per_bin() - - n_per_bin = np.array([len(values) for values in self.rx_values_lists]) - # Index of first not filled bin, assumes that never all bins are filled - idx_end = np.argmin(n_per_bin == self.c.ES_n_per_bin) - - phase_diffs_values_lists = self._phase_diff_list_per_bin() - phase_diffs_values = _phase_diff_value_per_bin(phase_diffs_values_lists) - - self._plot_and_log(tx_values, rx_values, phase_diffs_values, phase_diffs_values_lists) - - tx_values_crop = np.array(tx_values, dtype=np.float32)[:idx_end] - rx_values_crop = np.array(rx_values, dtype=np.float32)[:idx_end] - phase_diffs_values_crop = np.array(phase_diffs_values, dtype=np.float32)[:idx_end] - return tx_values_crop, rx_values_crop, phase_diffs_values_crop, 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/GlobalConfig.py b/dpd/src/GlobalConfig.py deleted file mode 100644 index 56839fc..0000000 --- a/dpd/src/GlobalConfig.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, constants and global configuration -# -# Source for DAB standard: etsi_EN_300_401_v010401p p145 -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -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 - - self.plot_location = plot_location - - # DAB frame - # Time domain - oversample = int(self.sample_rate / 2048000) - self.T_F = oversample * 196608 # Transmission frame duration - self.T_NULL = oversample * 2656 # Null symbol duration - self.T_S = oversample * 2552 # Duration of OFDM symbols of indices l = 1, 2, 3,... L; - self.T_U = oversample * 2048 # Inverse of carrier spacing - self.T_C = oversample * 504 # Duration of cyclic prefix - - # Frequency Domain - # example: np.delete(fft[3328:4865], 768) - self.FFT_delta = 1536 # Number of carrier frequencies - self.FFT_delete = 768 - self.FFT_start = 3328 - self.FFT_end = 4865 - - # Calculate sample offset from phase rotation - # 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. / self.sample_rate * 2 * np.pi * 1000 - - # Constants for ExtractStatistic - 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 - self.ES_n_per_bin = 128 # Number of measurements pre bin - - # Constants for Measure_Shoulder - self.MS_enable = False - 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] - shoulder_offset_edge = np.abs(meas_offset - self.FFT_delta) - self.MS_shoulder_left_start = self.FFT_start - shoulder_offset_edge - meas_width / 2 - self.MS_shoulder_left_end = self.FFT_start - shoulder_offset_edge + meas_width / 2 - self.MS_shoulder_right_start = self.FFT_end + shoulder_offset_edge - meas_width / 2 - self.MS_shoulder_right_end = self.FFT_end + shoulder_offset_edge + meas_width / 2 - self.MS_peak_start = self.FFT_start + 100 # Ignore region near edges - 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 - self.MS_n_proc = 4 - - # Constants for MER - self.MER_plot = cli_args.plot - - # Constants for Model - 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 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 B200 specific - self.RAGC_rx_median_target = cli_args.target_median - -# 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. diff --git a/dpd/src/Heuristics.py b/dpd/src/Heuristics.py deleted file mode 100644 index 21d400b..0000000 --- a/dpd/src/Heuristics.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, heuristics we use to tune the parameters. -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -import numpy as np - - -def get_learning_rate(idx_run): - """Gradually reduce learning rate from lr_max to lr_min within - idx_max steps, then keep the learning rate at lr_min""" - idx_max = 10.0 - lr_min = 0.05 - lr_max = 0.4 - lr_delta = lr_max - lr_min - idx_run = min(idx_run, idx_max) - learning_rate = lr_max - lr_delta * idx_run / idx_max - return learning_rate - - -def get_n_meas(idx_run): - """Gradually increase number of measurements used to extract - a statistic from n_meas_min to n_meas_max within idx_max steps, - then keep number of measurements at n_meas_max""" - idx_max = 10.0 - n_meas_min = 10 - n_meas_max = 20 - n_meas_delta = n_meas_max - n_meas_min - idx_run = min(idx_run, idx_max) - learning_rate = n_meas_delta * idx_run / idx_max + n_meas_min - return int(np.round(learning_rate)) - -# 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. diff --git a/dpd/src/MER.py b/dpd/src/MER.py deleted file mode 100644 index 693058d..0000000 --- a/dpd/src/MER.py +++ /dev/null @@ -1,132 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, Modulation Error Rate. -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -import datetime -import os -import logging -import numpy as np -import matplotlib -matplotlib.use('agg') -import matplotlib.pyplot as plt - -class MER: - def __init__(self, c): - self.c = c - - self.plot = c.MER_plot - - def _calc_spectrum(self, tx): - fft = np.fft.fftshift(np.fft.fft(tx)) - return np.delete(fft[self.c.FFT_start:self.c.FFT_end], - self.c.FFT_delete) - - def _split_in_carrier(self, x, y): - if 0.5 < np.mean((np.abs(np.abs(x) - np.abs(y)) / - np.abs(np.abs(x) + np.abs(y)))): - # Constellation points are axis aligned on the Im/Re plane - x1 = x[(y < x) & (y > -x)] - y1 = y[(y < x) & (y > -x)] - - x2 = x[(y > x) & (y > -x)] - y2 = y[(y > x) & (y > -x)] - - x3 = x[(y > x) & (y < -x)] - y3 = y[(y > x) & (y < -x)] - - x4 = x[(y < x) & (y < -x)] - y4 = y[(y < x) & (y < -x)] - else: - # Constellation points are on the diagonal or Im/Re plane - x1 = x[(+x > 0) & (+y > 0)] - y1 = y[(+x > 0) & (+y > 0)] - - x2 = x[(-x > 0) & (+y > 0)] - y2 = y[(-x > 0) & (+y > 0)] - - x3 = x[(-x > 0) & (-y > 0)] - y3 = y[(-x > 0) & (-y > 0)] - - x4 = x[(+x > 0) & (-y > 0)] - y4 = y[(+x > 0) & (-y > 0)] - return (x1, y1), (x2, y2), (x3, y3), (x4, y4) - - def _calc_mer_for_isolated_constellation_point(self, x, y): - """Calculate MER for one constellation point""" - x_mean = np.mean(x) - y_mean = np.mean(y) - - U_RMS = np.sqrt(x_mean ** 2 + y_mean ** 2) - U_ERR = np.mean(np.sqrt((x - x_mean) ** 2 + (y - y_mean) ** 2)) - MER = 20 * np.log10(U_ERR / U_RMS) - - return x_mean, y_mean, U_RMS, U_ERR, MER - - def calc_mer(self, tx, debug_name=""): - """Calculate MER for input signal from a symbol aligned signal.""" - assert tx.shape[0] == self.c.T_U, "Wrong input length" - - spectrum = self._calc_spectrum(tx) - - if self.plot and self.c.plot_location is not None: - dt = datetime.datetime.now().isoformat() - 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( - np.real(spectrum), - np.imag(spectrum))): - x_mean, y_mean, U_RMS, U_ERR, MER =\ - self._calc_mer_for_isolated_constellation_point(x, y) - MERs.append(MER) - - tau = np.linspace(0, 2 * np.pi, num=100) - x_err = U_ERR * np.sin(tau) + x_mean - y_err = U_ERR * np.cos(tau) + y_mean - - if self.plot: - ax = plt.subplot(221 + i) - ax.scatter(x, y, s=0.2, color='black') - ax.plot(x_mean, y_mean, 'p', color='red') - ax.plot(x_err, y_err, linewidth=2, color='blue') - ax.text(0.1, 0.1, "MER {:.1f}dB".format(MER), transform=ax.transAxes) - ax.set_xlabel("Real") - ax.set_ylabel("Imag") - ylim = ax.get_ylim() - ax.set_ylim(ylim[0] - (ylim[1] - ylim[0]) * 0.1, ylim[1]) - - if fig_path is not None: - plt.tight_layout() - plt.savefig(fig_path) - plt.show() - plt.close() - - MER_res = 20 * np.log10(np.mean([10 ** (MER / 20) for MER in MERs])) - return MER_res - -# 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/Measure.py b/dpd/src/Measure.py deleted file mode 100644 index 6d8007d..0000000 --- a/dpd/src/Measure.py +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, Measure signal using ODR-DabMod's -# DPD Server. -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -import socket -import struct -import numpy as np -import src.Dab_Util as DU -import os -import logging - -class Measure: - """Collect Measurement from DabMod""" - 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 - self.num_samples_to_request = num_samples_to_request - - def _recv_exact(self, sock, num_bytes): - """Receive an exact number of bytes from a socket. This is - a wrapper around sock.recv() that can return less than the number - of requested bytes. - - Args: - sock (socket): Socket to receive data from. - num_bytes (int): Number of bytes that will be returned. - """ - bufs = [] - while num_bytes > 0: - b = sock.recv(num_bytes) - if len(b) == 0: - break - num_bytes -= len(b) - bufs.append(b) - return b''.join(bufs) - - def receive_tcp(self): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(4) - s.connect(('localhost', self.port)) - - logging.debug("Send version") - s.sendall(b"\x01") - - logging.debug("Send request for {} samples".format(self.num_samples_to_request)) - s.sendall(struct.pack("=I", self.num_samples_to_request)) - - logging.debug("Wait for TX metadata") - num_samps, tx_second, tx_pps = struct.unpack("=III", self._recv_exact(s, 12)) - tx_ts = tx_second + tx_pps / 16384000.0 - - if num_samps > 0: - logging.debug("Receiving {} TX samples".format(num_samps)) - txframe_bytes = self._recv_exact(s, num_samps * self.sizeof_sample) - txframe = np.fromstring(txframe_bytes, dtype=np.complex64) - else: - txframe = np.array([], dtype=np.complex64) - - logging.debug("Wait for RX metadata") - rx_second, rx_pps = struct.unpack("=II", self._recv_exact(s, 8)) - rx_ts = rx_second + rx_pps / 16384000.0 - - if num_samps > 0: - logging.debug("Receiving {} RX samples".format(num_samps)) - rxframe_bytes = self._recv_exact(s, num_samps * self.sizeof_sample) - rxframe = np.fromstring(rxframe_bytes, dtype=np.complex64) - else: - rxframe = np.array([], dtype=np.complex64) - - if logging.getLogger().getEffectiveLevel() == logging.DEBUG: - logging.debug('txframe: min {}, max {}, median {}'.format( - np.min(np.abs(txframe)), - np.max(np.abs(txframe)), - np.median(np.abs(txframe)))) - - logging.debug('rxframe: min {}, max {}, median {}'.format( - np.min(np.abs(rxframe)), - np.max(np.abs(rxframe)), - np.median(np.abs(rxframe)))) - - logging.debug("Disconnecting") - s.close() - - 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, rxframe_aligned, rx_ts, rx_median) - """ - - txframe, tx_ts, rxframe, rx_ts = self.receive_tcp() - - # Normalize received signal with sent signal - rx_median = np.median(np.abs(rxframe)) - rxframe = rxframe / rx_median * np.median(np.abs(txframe)) - - du = DU.Dab_Util(self.c, self.samplerate) - txframe_aligned, rxframe_aligned = du.subsample_align(txframe, rxframe) - - logging.info( - "Measurement done, tx %d %s, rx %d %s, tx aligned %d %s, rx aligned %d %s" - % (len(txframe), txframe.dtype, len(rxframe), rxframe.dtype, - len(txframe_aligned), txframe_aligned.dtype, len(rxframe_aligned), rxframe_aligned.dtype) ) - - return txframe_aligned, tx_ts, rxframe_aligned, rx_ts, rx_median - -# 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/Measure_Shoulders.py b/dpd/src/Measure_Shoulders.py deleted file mode 100644 index fd90050..0000000 --- a/dpd/src/Measure_Shoulders.py +++ /dev/null @@ -1,158 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation 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 -import multiprocessing -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) - - -def calc_fft_db(signal, offset, c): - fft = np.fft.fftshift(np.fft.fft(signal[offset:offset + c.MS_FFT_size])) - fft_db = 20 * np.log10(np.abs(fft)) - return fft_db - - -def _calc_peak(fft, c): - assert fft.shape == (c.MS_FFT_size,), fft.shape - idxs = (c.MS_peak_start, c.MS_peak_end) - peak = np.mean(fft[idxs[0]:idxs[1]]) - return peak, idxs - - -def _calc_shoulder_hight(fft_db, c): - assert fft_db.shape == (c.MS_FFT_size,), fft_db.shape - idxs_left = (c.MS_shoulder_left_start, c.MS_shoulder_left_end) - idxs_right = (c.MS_shoulder_right_start, 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(fft, c): - peak = _calc_peak(fft, c)[0] - shoulder = _calc_shoulder_hight(fft, c)[0] - assert (peak >= shoulder), (peak, shoulder) - return peak, shoulder - - -def shoulder_from_sig_offset(arg): - signal, offset, c = arg - fft_db = calc_fft_db(signal, offset, c) - peak, shoulder = calc_shoulder(fft_db, c) - return peak - shoulder, peak, shoulder - - -class Measure_Shoulders: - """Calculate difference between the DAB signal and the shoulder hight in the - power spectrum""" - - def __init__(self, c): - self.c = c - self.plot = c.MS_plot - - def _plot(self, signal): - if self.c.plot_location is None: - return - - dt = datetime.datetime.now().isoformat() - 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) - shoulder, idxs_sh = _calc_shoulder_hight(fft, self.c) - - 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(calc_shoulder(fft, self.c))) - - ax.set_ylim(-20, 30) - fig.tight_layout() - fig.savefig(fig_path) - plt.close(fig) - - def average_shoulders(self, signal, n_avg=None): - if not self.c.MS_enable: - logging.info("Shoulder Measurement disabled via Const.py") - return 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) - - args = zip( - [signal, ] * offsets.shape[0], - offsets, - [self.c, ] * offsets.shape[0] - ) - - pool = multiprocessing.Pool(self.c.MS_n_proc) - res = pool.map(shoulder_from_sig_offset, args) - shoulders_diff, shoulders, peaks = zip(*res) - - if logging.getLogger().getEffectiveLevel() == logging.DEBUG and self.plot: - self._plot(signal) - - return np.mean(shoulders_diff), np.mean(shoulders), np.mean(peaks) - -# 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.py b/dpd/src/Model.py deleted file mode 100644 index b2c303f..0000000 --- a/dpd/src/Model.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -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 -# 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. diff --git a/dpd/src/Model_AM.py b/dpd/src/Model_AM.py deleted file mode 100644 index 75b226f..0000000 --- a/dpd/src/Model_AM.py +++ /dev/null @@ -1,122 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation 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 -import numpy as np -import matplotlib.pyplot as plt - - -def is_npfloat32(array): - assert isinstance(array, np.ndarray), type(array) - assert array.dtype == np.float32, array.dtype - assert array.flags.contiguous - assert not any(np.isnan(array)) - - -def check_input_get_next_coefs(tx_dpd, rx_received): - is_npfloat32(tx_dpd) - is_npfloat32(rx_received) - - -def poly(sig): - return np.array([sig ** i for i in range(1, 6)]).T - - -def fit_poly(tx_abs, rx_abs): - return np.linalg.lstsq(poly(rx_abs), tx_abs, rcond=None)[0] - - -def calc_line(coefs, min_amp, max_amp): - rx_range = np.linspace(min_amp, max_amp) - tx_est = np.sum(poly(rx_range) * coefs, axis=1) - return tx_est, rx_range - - -class Model_AM: - """Calculates new coefficients using the measurement and the previous - coefficients""" - - def __init__(self, - c, - learning_rate_am=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 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 = 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)) - 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=1) - ax.set_title("Model_AM") - ax.set_xlabel("TX Amplitude") - ax.set_ylabel("RX Amplitude") - ax.set_xlim(-0.5, 1.5) - ax.legend(loc=4) - - fig.tight_layout() - fig.savefig(fig_path) - plt.close(fig) - - def get_next_coefs(self, tx_dpd, rx_received, coefs_am): - """Calculate the next AM/AM coefficients using the extracted - statistic of TX and RX amplitude""" - check_input_get_next_coefs(tx_dpd, rx_received) - - coefs_am_new = fit_poly(tx_dpd, rx_received) - coefs_am_new = coefs_am + \ - self.learning_rate_am * (coefs_am_new - coefs_am) - - 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_Lut.py b/dpd/src/Model_Lut.py deleted file mode 100644 index e70fdb0..0000000 --- a/dpd/src/Model_Lut.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, model implementation using polynomial -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -import os -import logging -import numpy as np - -class Lut: - """Implements a model that calculates lookup table coefficients""" - - def __init__(self, - c, - learning_rate=1., - plot=False): - """ - - :rtype: - """ - logging.debug("Initialising LUT Model") - self.c = c - self.learning_rate = learning_rate - self.plot = plot - self.reset_coefs() - - def reset_coefs(self): - self.scalefactor = 0xFFFFFFFF # max uint32_t value - self.lut = np.ones(32, dtype=np.complex64) - - def train(self, tx_abs, rx_abs, phase_diff): - pass - - def get_dpd_data(self): - return "lut", self.scalefactor, self.lut - -# 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. diff --git a/dpd/src/Model_PM.py b/dpd/src/Model_PM.py deleted file mode 100644 index 7b80bf3..0000000 --- a/dpd/src/Model_PM.py +++ /dev/null @@ -1,124 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation 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 -import numpy as np -import matplotlib.pyplot as plt - - -def is_npfloat32(array): - assert isinstance(array, np.ndarray), type(array) - assert array.dtype == np.float32, array.dtype - assert array.flags.contiguous - assert not any(np.isnan(array)) - - -def check_input_get_next_coefs(tx_dpd, phase_diff): - is_npfloat32(tx_dpd) - is_npfloat32(phase_diff) - - -class Model_PM: - """Calculates new coefficients using the measurement and the previous - coefficients""" - - def __init__(self, - c, - learning_rate_pm=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 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 = 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)) - 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=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 _discard_small_values(self, tx_dpd, phase_diff): - """ Assumes that the phase for small tx amplitudes is zero""" - mask = tx_dpd < self.c.MPM_tx_min - phase_diff[mask] = 0 - return tx_dpd, phase_diff - - 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, rcond=None)[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): - """Calculate the next AM/PM coefficients using the extracted - statistic of TX amplitude and phase difference""" - tx_dpd, phase_diff = self._discard_small_values(tx_dpd, phase_diff) - check_input_get_next_coefs(tx_dpd, phase_diff) - - coefs_pm_new = self.fit_poly(tx_dpd, phase_diff) - - coefs_pm_new = coefs_pm + self.learning_rate_pm * (coefs_pm_new - coefs_pm) - 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 deleted file mode 100644 index cdfd319..0000000 --- a/dpd/src/Model_Poly.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, model implementation using polynomial -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -import os -import logging -import numpy as np - -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): - self.c = c - self.plot = c.MDL_plot - - 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=self.plot) - self.model_pm = Model_PM.Model_PM(c, plot=self.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) - - def train(self, tx_abs, rx_abs, phase_diff, lr=None): - """ - :type tx_abs: np.ndarray - :type rx_abs: np.ndarray - :type phase_diff: np.ndarray - :type lr: float - """ - _check_input_get_next_coefs(tx_abs, rx_abs, phase_diff) - - if not lr is None: - self.model_am.learning_rate_am = lr - self.model_pm.learning_rate_pm = lr - - 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/RX_Agc.py b/dpd/src/RX_Agc.py deleted file mode 100644 index f778dee..0000000 --- a/dpd/src/RX_Agc.py +++ /dev/null @@ -1,166 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Automatic Gain Control -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -import datetime -import os -import logging -import time -import numpy as np -import matplotlib -matplotlib.use('agg') -import matplotlib.pyplot as plt - -import src.Adapt as Adapt -import src.Measure as Measure - -class Agc: - """The goal of the automatic gain control is to set the - RX gain to a value at which all received amplitudes can - be detected. This means that the maximum possible amplitude - should be quantized at the highest possible digital value. - - A problem we have to face, is that the estimation of the - maximum amplitude by applying the max() function is very - unstable. This is due to the maximum’s rareness. Therefore - we estimate a far more robust value, such as the median, - and then approximate the maximum amplitude from it. - - Given this, we tune the RX gain in such a way, that the - received signal fulfills our desired property, of having - all amplitudes properly quantized.""" - - def __init__(self, measure, adapt, c): - assert isinstance(measure, Measure.Measure) - assert isinstance(adapt, Adapt.Adapt) - self.measure = measure - self.adapt = adapt - self.min_rxgain = c.RAGC_min_rxgain - self.rxgain = self.min_rxgain - self.peak_to_median = 1./c.RAGC_rx_median_target - - def run(self): - self.adapt.set_rxgain(self.rxgain) - - for i in range(2): - # Measure - txframe_aligned, tx_ts, rxframe_aligned, rx_ts, rx_median= \ - self.measure.get_samples() - - # Estimate Maximum - rx_peak = self.peak_to_median * rx_median - correction_factor = 20*np.log10(1/rx_peak) - self.rxgain = self.rxgain + correction_factor - - assert self.min_rxgain <= self.rxgain, ("Desired RX Gain is {} which is smaller than the minimum of {}".format( - self.rxgain, self.min_rxgain)) - - logging.info("RX Median {:1.4f}, estimated peak {:1.4f}, correction factor {:1.4f}, new RX gain {:1.4f}".format( - rx_median, rx_peak, correction_factor, self.rxgain - )) - - self.adapt.set_rxgain(self.rxgain) - time.sleep(0.5) - - 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 = self.c.plot_location + "/" + dt + "_agc.png" - fig, axs = plt.subplots(2, 2, figsize=(3*6,1*6)) - axs = axs.ravel() - - for j in range(5): - txframe_aligned, tx_ts, rxframe_aligned, rx_ts, rx_median =\ - self.measure.get_samples() - - rxframe_aligned_abs = np.abs(rxframe_aligned) - - x = np.arange(100, rxframe_aligned_abs.shape[0], dtype=int) - rx_max_until = [] - rx_median_until = [] - rx_mean_until = [] - for i in x: - rx_max_until.append(np.max(rxframe_aligned_abs[:i])) - rx_median_until.append(np.median(rxframe_aligned_abs[:i])) - rx_mean_until.append(np.mean(rxframe_aligned_abs[:i])) - - axs[0].plot(x, - rx_max_until, - label="Run {}".format(j+1), - color=matplotlib.colors.hsv_to_rgb((1./(j+1.),0.8,0.7)), - linestyle="-", linewidth=0.25) - axs[0].set_xlabel("Samples") - axs[0].set_ylabel("Amplitude") - axs[0].set_title("Estimation for Maximum RX Amplitude") - axs[0].legend() - - axs[1].plot(x, - rx_median_until, - label="Run {}".format(j+1), - color=matplotlib.colors.hsv_to_rgb((1./(j+1.),0.9,0.7)), - linestyle="-", linewidth=0.25) - axs[1].set_xlabel("Samples") - axs[1].set_ylabel("Amplitude") - axs[1].set_title("Estimation for Median RX Amplitude") - axs[1].legend() - ylim_1 = axs[1].get_ylim() - - axs[2].plot(x, - rx_mean_until, - label="Run {}".format(j+1), - color=matplotlib.colors.hsv_to_rgb((1./(j+1.),0.9,0.7)), - linestyle="-", linewidth=0.25) - axs[2].set_xlabel("Samples") - axs[2].set_ylabel("Amplitude") - axs[2].set_title("Estimation for Mean RX Amplitude") - ylim_2 = axs[2].get_ylim() - axs[2].legend() - - axs[1].set_ylim(min(ylim_1[0], ylim_2[0]), - max(ylim_1[1], ylim_2[1])) - - fig.tight_layout() - fig.savefig(fig_path) - - axs[3].hist(rxframe_aligned_abs, bins=60) - axs[3].set_xlabel("Amplitude") - axs[3].set_ylabel("Frequency") - axs[3].set_title("Histogram of Amplitudes") - axs[3].legend() - - fig.tight_layout() - fig.savefig(fig_path) - plt.close(fig) - - -# 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 deleted file mode 100644 index 2a17a65..0000000 --- a/dpd/src/Symbol_align.py +++ /dev/null @@ -1,193 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, Modulation Error Rate. -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -import datetime -import os -import logging -import numpy as np -import scipy -import matplotlib - -matplotlib.use('agg') -import matplotlib.pyplot as plt - - -def _remove_outliers(x, stds=5): - deviation_from_mean = np.abs(x - np.mean(x)) - inlier_idxs = deviation_from_mean < stds * np.std(x) - x = x[inlier_idxs] - return x - - -def _calc_delta_angle(fft): - # Introduce invariance against carrier - angles = np.angle(fft) % (np.pi / 2.) - - # Calculate Angle difference and compensate jumps - deltas_angle = np.diff(angles) - deltas_angle[deltas_angle > np.pi / 4.] = \ - deltas_angle[deltas_angle > np.pi / 4.] - np.pi / 2. - deltas_angle[-deltas_angle > np.pi / 4.] = \ - deltas_angle[-deltas_angle > np.pi / 4.] + np.pi / 2. - deltas_angle = _remove_outliers(deltas_angle) - - delta_angle = np.mean(deltas_angle) - - return delta_angle - - -class Symbol_align: - """ - Find the phase offset to the start of the DAB symbols in an - unaligned dab signal. - """ - - def __init__(self, c, plot=False): - self.c = c - self.plot = plot - pass - - def _calc_offset_to_first_symbol_without_prefix(self, tx): - tx_orig = tx[0:-self.c.T_U] - tx_cut_prefix = tx[self.c.T_U:] - - tx_product = np.abs(tx_orig - tx_cut_prefix) - tx_product_avg = np.correlate( - tx_product, - np.ones(self.c.T_C), - mode='valid') - tx_product_avg_min_filt = \ - scipy.ndimage.filters.minimum_filter1d( - tx_product_avg, - int(1.5 * self.c.T_S) - ) - peaks = np.ravel(np.where(tx_product_avg == tx_product_avg_min_filt)) - - offset = peaks[np.argmin([tx_product_avg[peak] for peak in peaks])] - - if self.plot and self.c.plot_location is not None: - dt = datetime.datetime.now().isoformat() - fig_path = self.c.plot_location + "/" + dt + "_Symbol_align.png" - - fig = plt.figure(figsize=(9, 9)) - - ax = fig.add_subplot(4, 1, 1) - ax.plot(tx_product) - ylim = ax.get_ylim() - for peak in peaks: - ax.plot((peak, peak), (ylim[0], ylim[1])) - if peak == offset: - ax.text(peak, ylim[0] + 0.3 * np.diff(ylim), "offset", rotation=90) - else: - ax.text(peak, ylim[0] + 0.2 * np.diff(ylim), "peak", rotation=90) - ax.set_xlabel("Sample") - ax.set_ylabel("Conj. Product") - ax.set_title("Difference with shifted self") - - ax = fig.add_subplot(4, 1, 2) - ax.plot(tx_product_avg) - ylim = ax.get_ylim() - for peak in peaks: - ax.plot((peak, peak), (ylim[0], ylim[1])) - if peak == offset: - ax.text(peak, ylim[0] + 0.3 * np.diff(ylim), "offset", rotation=90) - else: - ax.text(peak, ylim[0] + 0.2 * np.diff(ylim), "peak", rotation=90) - ax.set_xlabel("Sample") - ax.set_ylabel("Conj. Product") - ax.set_title("Moving Average") - - ax = fig.add_subplot(4, 1, 3) - ax.plot(tx_product_avg_min_filt) - ylim = ax.get_ylim() - for peak in peaks: - ax.plot((peak, peak), (ylim[0], ylim[1])) - if peak == offset: - ax.text(peak, ylim[0] + 0.3 * np.diff(ylim), "offset", rotation=90) - else: - ax.text(peak, ylim[0] + 0.2 * np.diff(ylim), "peak", rotation=90) - ax.set_xlabel("Sample") - ax.set_ylabel("Conj. Product") - ax.set_title("Min Filter") - - ax = fig.add_subplot(4, 1, 4) - tx_product_crop = tx_product[peaks[0] - 50:peaks[0] + 50] - x = range(tx_product.shape[0])[peaks[0] - 50:peaks[0] + 50] - ax.plot(x, tx_product_crop) - ylim = ax.get_ylim() - ax.plot((peaks[0], peaks[0]), (ylim[0], ylim[1])) - ax.set_xlabel("Sample") - ax.set_ylabel("Conj. Product") - ax.set_title("Difference with shifted self") - - fig.tight_layout() - fig.savefig(fig_path) - plt.close(fig) - - # "offset" measures where the shifted signal matches the - # original signal. Therefore we have to subtract the size - # of the shift to find the offset of the symbol start. - return (offset + self.c.T_C) % self.c.T_S - - def _delta_angle_to_samples(self, angle): - return - angle / self.c.phase_offset_per_sample - - def _calc_sample_offset(self, sig): - assert sig.shape[0] == self.c.T_U, \ - "Input length is not a Symbol without cyclic prefix" - - fft = np.fft.fftshift(np.fft.fft(sig)) - fft_crop = np.delete(fft[self.c.FFT_start:self.c.FFT_end], self.c.FFT_delete) - delta_angle = _calc_delta_angle(fft_crop) - delta_sample = self._delta_angle_to_samples(delta_angle) - 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. Error {}".format(error)) - return delta_sample_int - - def calc_offset(self, tx): - """Calculate the offset the first symbol""" - off_sym = self._calc_offset_to_first_symbol_without_prefix( - tx) - off_sam = self._calc_sample_offset( - tx[off_sym:off_sym + self.c.T_U]) - off = (off_sym + off_sam) % self.c.T_S - - assert self._calc_sample_offset(tx[off:off + self.c.T_U]) == 0, \ - "Failed to calculate offset" - return off - - def crop_symbol_without_cyclic_prefix(self, tx): - off = self.calc_offset(tx) - return tx[ - off: - off + self.c.T_U - ] - -# 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/TX_Agc.py b/dpd/src/TX_Agc.py deleted file mode 100644 index 309193d..0000000 --- a/dpd/src/TX_Agc.py +++ /dev/null @@ -1,131 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, Automatic Gain Control. -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -import datetime -import os -import logging -import time -import numpy as np -import matplotlib - -matplotlib.use('agg') -import matplotlib.pyplot as plt - -import src.Adapt as Adapt - - -# TODO fix for float tx_gain -class TX_Agc: - def __init__(self, - adapt, - c): - """ - In order to avoid digital clipping, this class increases the - TX gain and reduces the digital gain. Digital clipping happens - when the digital analog converter receives values greater than - it's maximal output. This class solves that problem by adapting - the TX gain in a way that the peaks of the TX signal are in a - specified range. The TX gain is adapted accordingly. The TX peaks - are approximated by estimating it based on the signal median. - - :param adapt: Instance of Adapt Class to update - txgain and coefficients - :param max_txgain: limit for TX gain - :param tx_median_threshold_max: if the median of TX is larger - than this value, then the digital gain is reduced - :param tx_median_threshold_min: if the median of TX is smaller - than this value, then the digital gain is increased - :param tx_median_target: The digital gain is reduced in a way that - the median TX value is expected to be lower than this value. - """ - - assert isinstance(adapt, Adapt.Adapt) - self.adapt = adapt - self.max_txgain = c.TAGC_max_txgain - self.txgain = self.max_txgain - - self.tx_median_threshold_tolerate_max = c.TAGC_tx_median_max - self.tx_median_threshold_tolerate_min = c.TAGC_tx_median_min - self.tx_median_target = c.TAGC_tx_median_target - - def _calc_new_tx_gain(self, tx_median): - delta_db = 20 * np.log10(self.tx_median_target / tx_median) - new_txgain = self.adapt.get_txgain() - delta_db - assert new_txgain < self.max_txgain, \ - "TX_Agc failed. New TX gain of {} is too large.".format( - new_txgain - ) - return new_txgain, delta_db - - def _calc_digital_gain(self, delta_db): - digital_gain_factor = 10 ** (delta_db / 20.) - digital_gain = self.adapt.get_digital_gain() * digital_gain_factor - return digital_gain, digital_gain_factor - - def _set_tx_gain(self, new_txgain): - self.adapt.set_txgain(new_txgain) - txgain = self.adapt.get_txgain() - return txgain - - def _have_to_adapt(self, tx_median): - too_large = tx_median > self.tx_median_threshold_tolerate_max - too_small = tx_median < self.tx_median_threshold_tolerate_min - return too_large or too_small - - def adapt_if_necessary(self, tx): - tx_median = np.median(np.abs(tx)) - - if self._have_to_adapt(tx_median): - # Calculate new values - new_txgain, delta_db = self._calc_new_tx_gain(tx_median) - digital_gain, digital_gain_factor = \ - self._calc_digital_gain(delta_db) - - # Set new values. - # Avoid temorary increase of output power with correct order - if digital_gain_factor < 1: - self.adapt.set_digital_gain(digital_gain) - time.sleep(0.5) - txgain = self._set_tx_gain(new_txgain) - time.sleep(1) - else: - txgain = self._set_tx_gain(new_txgain) - time.sleep(1) - self.adapt.set_digital_gain(digital_gain) - time.sleep(0.5) - - logging.info( - "digital_gain = {}, txgain_new = {}, " \ - "delta_db = {}, tx_median {}, " \ - "digital_gain_factor = {}". - format(digital_gain, txgain, delta_db, - tx_median, digital_gain_factor)) - - return True - return False - -# 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/__init__.py b/dpd/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/dpd/src/phase_align.py b/dpd/src/phase_align.py deleted file mode 100644 index 8654333..0000000 --- a/dpd/src/phase_align.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, phase-align a signal against a reference. -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file -import datetime -import os -import logging -import numpy as np -import matplotlib.pyplot as plt - - -def phase_align(sig, ref_sig, plot=False): - """Do phase alignment for sig relative to the reference signal - ref_sig. - - Returns the aligned signal""" - - angle_diff = (np.angle(sig) - np.angle(ref_sig)) % (2. * np.pi) - - real_diffs = np.cos(angle_diff) - imag_diffs = np.sin(angle_diff) - - if plot and self.c.plot_location is not None: - dt = datetime.datetime.now().isoformat() - fig_path = self.c.plot_location + "/" + dt + "_phase_align.png" - - plt.subplot(511) - plt.hist(angle_diff, bins=60, label="Angle Diff") - plt.xlabel("Angle") - plt.ylabel("Count") - plt.legend(loc=4) - - plt.subplot(512) - plt.hist(real_diffs, bins=60, label="Real Diff") - plt.xlabel("Real Part") - plt.ylabel("Count") - plt.legend(loc=4) - - plt.subplot(513) - plt.hist(imag_diffs, bins=60, label="Imaginary Diff") - plt.xlabel("Imaginary Part") - plt.ylabel("Count") - plt.legend(loc=4) - - plt.subplot(514) - plt.plot(np.angle(ref_sig[:128]), label="ref_sig") - plt.plot(np.angle(sig[:128]), label="sig") - plt.xlabel("Angle") - plt.ylabel("Sample") - plt.legend(loc=4) - - real_diff = np.median(real_diffs) - imag_diff = np.median(imag_diffs) - - angle = np.angle(real_diff + 1j * imag_diff) - - logging.debug( - "Compensating phase by {} rad, {} degree. real median {}, imag median {}".format( - angle, angle*180./np.pi, real_diff, imag_diff - )) - sig = sig * np.exp(1j * -angle) - - if logging.getLogger().getEffectiveLevel() == logging.DEBUG and plot: - plt.subplot(515) - plt.plot(np.angle(ref_sig[:128]), label="ref_sig") - plt.plot(np.angle(sig[:128]), label="sig") - plt.xlabel("Angle") - plt.ylabel("Sample") - plt.legend(loc=4) - plt.tight_layout() - plt.savefig(fig_path) - plt.close() - - return sig - -# 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/subsample_align.py b/dpd/src/subsample_align.py deleted file mode 100755 index 20ae56b..0000000 --- a/dpd/src/subsample_align.py +++ /dev/null @@ -1,111 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, utility to do subsample alignment. -# -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file -import datetime -import logging -import os -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.") - - halflength = int(length / 2) - factor = 2.0 * np.pi / length - - omega = np.zeros(length, dtype=np.float) - for i in range(halflength): - omega[i] = factor * i - - for i in range(halflength, length): - omega[i] = factor * (i - length) - - return omega - - -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 - - Returns the aligned signal""" - - n = len(sig) - if (n % 2) == 1: - raise ValueError("Needs an even length signal.") - halflen = int(n / 2) - - fft_sig = np.fft.fft(sig) - - omega = gen_omega(n) - - def correlate_for_delay(tau): - # A subsample offset between two signals corresponds, in the frequency - # domain, to a linearly increasing phase shift, whose slope - # corresponds to the delay. - # - # Here, we build this phase shift in rotate_vec, and multiply it with - # our signal. - - rotate_vec = np.exp(1j * tau * omega) - # zero-frequency is rotate_vec[0], so rotate_vec[N/2] is the - # bin corresponding to the [-1, 1, -1, 1, ...] time signal, which - # is both the maximum positive and negative frequency. - # I don't remember why we handle it differently. - rotate_vec[halflen] = np.cos(np.pi * tau) - - corr_sig = np.fft.ifft(rotate_vec * fft_sig) - - return -np.abs(np.sum(np.conj(corr_sig) * ref_sig)) - - optim_result = optimize.minimize_scalar(correlate_for_delay, bounds=(-1, 1), method='bounded', - options={'disp': True}) - - if optim_result.success: - best_tau = optim_result.x - - 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 = (plot_location + "/" + dt + "_tau.png") - plt.plot(ixs, taus) - plt.title("Subsample correlation, minimum is best: {}".format(best_tau)) - plt.savefig(tau_path) - plt.close() - - # Prepare rotate_vec = fft_sig with rotated phase - rotate_vec = np.exp(1j * best_tau * omega) - rotate_vec[halflen] = np.cos(np.pi * best_tau) - return np.fft.ifft(rotate_vec * fft_sig).astype(np.complex64) - else: - # print("Could not optimize: " + optim_result.message) - return np.zeros(0, dtype=np.complex64) - -# 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/store_received.py b/dpd/store_received.py deleted file mode 100755 index 19b735e..0000000 --- a/dpd/store_received.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# This is an example tool that shows how to connect to ODR-DabMod's dpd TCP server -# and get samples from there. -# -# Since the TX and RX samples are not perfectly aligned, the tool has to align them properly, -# which is done in two steps: First on sample-level using a correlation, then with subsample -# accuracy using a FFT approach. -# -# It requires SciPy and matplotlib. -# -# Copyright (C) 2017 Matthias P. Braendli -# http://www.opendigitalradio.org -# Licence: The MIT License, see notice at the end of this file - -import sys -import socket -import struct -import argparse -import os -import time -from src.GlobalConfig import GlobalConfig -from src.Measure import Measure - -SIZEOF_SAMPLE = 8 # complex floats - -parser = argparse.ArgumentParser(description="Plot the spectrum of ODR-DabMod's DPD feedback") -parser.add_argument('--samps', default='10240', type=int, - help='Number of samples to request at once', - required=False) -parser.add_argument('--port', default='50055', type=int, - help='port to connect to ODR-DabMod DPD (default: 50055)', - required=False) -parser.add_argument('--count', default='1', type=int, - help='Number of recordings', - required=False) -parser.add_argument('--verbose', type=int, default=0, - help='Level of verbosity', - required=False) -parser.add_argument('--plot', - help='Enable all plots, to be more selective choose plots in GlobalConfig.py', - action="store_true") -parser.add_argument('--samplerate', default=8192000, type=int, - help='Sample rate', - required=False) - -cli_args = parser.parse_args() - -cli_args.target_median = 0.05 - -c = GlobalConfig(cli_args, None) - -meas = Measure(c, cli_args.samplerate, cli_args.port, cli_args.samps) - -for i in range(int(cli_args.count)): - if i>0: - time.sleep(0.1) - - txframe_aligned, tx_ts, rxframe_aligned, rx_ts, rx_median = meas.get_samples() - - txframe_aligned.tofile("%d_tx_record.iq" % i) - rxframe_aligned.tofile("%d_rx_record.iq" % i) - -# The MIT License (MIT) -# -# Copyright (c) 2018 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. diff --git a/gui/README.md b/gui/README.md deleted file mode 100644 index ec55bc9..0000000 --- a/gui/README.md +++ /dev/null @@ -1,45 +0,0 @@ -ODR-DabMod Web UI -================= - -Goals ------ - -Enable users to play with digital predistortion settings, through a -visualisation of the settings and the parameters. - -Make it easier to discover the tuning possibilities of the modulator. - - -Install -------- - -Install dependencies: cherrypy, jinja2, scipy, matplotlib, zmq python modules - -Run ---- - -1. Execute ODR-DabMod, configured with zmq rc on port 9400 -1. `cd gui` -1. `./run.py` -1. Connect your browser to `http://localhost:8099` - -Todo ----- - -* Integrate DPDCE - * Show DPD settings and effect visually - * Allow load/store of DPD settings - * Make ports configurable -* Use Feedback Server interface and make spectrum and constellation plots -* Get authentication to work -* Read and write config file, and add forms to change ODR-DabMod configuration -* Connect to supervisord to be able to restart ODR-DabMod -* Create a status page - * Is process running? - * Is modulator rate within bounds? - * Are there underruns or late packets? - * Is the GPSDO ok? (needs new RC params) -* Think about how to show PA settings - * Return loss is an important metric - * Some PAs offer serial interfaces for supervision - diff --git a/gui/api/__init__.py b/gui/api/__init__.py deleted file mode 100755 index 77faa10..0000000 --- a/gui/api/__init__.py +++ /dev/null @@ -1,132 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2018 -# Matthias P. Braendli, matthias.braendli@mpb.li -# -# http://www.opendigitalradio.org -# -# This file is part of ODR-DabMod. -# -# ODR-DabMod 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 of the -# License, or (at your option) any later version. -# -# ODR-DabMod 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 ODR-DabMod. If not, see . - -import cherrypy -from cherrypy.lib.httputil import parse_query_string - -import json -import urllib -import os - -import io -import datetime -import threading - -def send_ok(data=None): - if data is not None: - return {'status' : 'ok', 'data': data} - else: - return {'status': 'ok'} - -def send_error(reason=""): - if reason: - return {'status' : 'error', 'reason': reason} - else: - return {'status' : 'error'} - -class RXThread(threading.Thread): - def __init__(self, api): - super(RXThread, self).__init__() - self.api = api - self.running = False - self.daemon = True - - def cancel(self): - self.running = False - - def run(self): - self.running = True - while self.running: - if self.api.dpd_pipe.poll(1): - rx = self.api.dpd_pipe.recv() - if rx['cmd'] == "quit": - break - elif rx['cmd'] == "dpd-state": - self.api.dpd_state = rx['data'] - elif rx['cmd'] == "dpd-calibration-result": - self.api.calibration_result = rx['data'] - -class API: - def __init__(self, mod_rc, dpd_pipe): - self.mod_rc = mod_rc - self.dpd_pipe = dpd_pipe - self.dpd_state = None - self.calibration_result = None - self.receive_thread = RXThread(self) - self.receive_thread.start() - - @cherrypy.expose - def index(self): - return """This is the api area.""" - - @cherrypy.expose - @cherrypy.tools.json_out() - def rc_parameters(self): - return send_ok(self.mod_rc.get_modules()) - - @cherrypy.expose - @cherrypy.tools.json_out() - def parameter(self, **kwargs): - if cherrypy.request.method == 'POST': - cl = cherrypy.request.headers['Content-Length'] - rawbody = cherrypy.request.body.read(int(cl)) - params = json.loads(rawbody.decode()) - try: - self.mod_rc.set_param_value(params['controllable'], params['param'], params['value']) - except ValueError as e: - cherrypy.response.status = 400 - return send_error(str(e)) - return send_ok() - else: - cherrypy.response.status = 400 - return send_error("POST only") - - @cherrypy.expose - @cherrypy.tools.json_out() - def trigger_capture(self, **kwargs): - if cherrypy.request.method == 'POST': - self.dpd_pipe.send({'cmd': "dpd-capture"}) - return send_ok() - else: - cherrypy.response.status = 400 - return send_error("POST only") - - @cherrypy.expose - @cherrypy.tools.json_out() - def dpd_status(self, **kwargs): - if self.dpd_state is not None: - return send_ok(self.dpd_state) - else: - return send_error("DPD state unknown") - - @cherrypy.expose - @cherrypy.tools.json_out() - def calibrate(self, **kwargs): - if cherrypy.request.method == 'POST': - self.dpd_pipe.send({'cmd': "dpd-calibrate"}) - return send_ok() - else: - if self.calibration_result is not None: - return send_ok(self.calibration_result) - else: - return send_error("DPD calibration result unknown") - diff --git a/gui/configuration.py b/gui/configuration.py deleted file mode 100644 index 4af1abd..0000000 --- a/gui/configuration.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2018 -# Matthias P. Braendli, matthias.braendli@mpb.li -# -# http://www.opendigitalradio.org -# -# This file is part of ODR-DabMod. -# -# ODR-DabMod 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 of the -# License, or (at your option) any later version. -# -# ODR-DabMod 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 ODR-DabMod. If not, see . -import json - -class ConfigurationNotPresent: - pass - -class Configuration: - def __init__(self, configfilename="ui-config.json"): - self.config = None - - try: - fd = open(configfilename, "r") - self.config = json.load(fd) - except json.JSONDecodeError: - pass - except OSError: - pass - - def get_key(self, key): - if self.config is None: - raise ConfigurationNotPresent() - else: - return self.config[key] diff --git a/gui/dpd/Align.py b/gui/dpd/Align.py deleted file mode 100644 index 1634ec8..0000000 --- a/gui/dpd/Align.py +++ /dev/null @@ -1,166 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, utility to do subsample alignment. -# -# Copyright (c) 2017 Andreas Steger -# Copyright (c) 2018 Matthias P. Braendli -# -# http://www.opendigitalradio.org -# -# This file is part of ODR-DabMod. -# -# ODR-DabMod 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 of the -# License, or (at your option) any later version. -# -# ODR-DabMod 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 ODR-DabMod. If not, see . -import datetime -import os -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.") - - halflength = int(length / 2) - factor = 2.0 * np.pi / length - - omega = np.zeros(length, dtype=np.float) - for i in range(halflength): - omega[i] = factor * i - - for i in range(halflength, length): - omega[i] = factor * (i - length) - - return omega - - -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 - - Returns the aligned signal""" - - n = len(sig) - if (n % 2) == 1: - raise ValueError("Needs an even length signal.") - halflen = int(n / 2) - - fft_sig = np.fft.fft(sig) - - omega = gen_omega(n) - - def correlate_for_delay(tau): - # A subsample offset between two signals corresponds, in the frequency - # domain, to a linearly increasing phase shift, whose slope - # corresponds to the delay. - # - # Here, we build this phase shift in rotate_vec, and multiply it with - # our signal. - - rotate_vec = np.exp(1j * tau * omega) - # zero-frequency is rotate_vec[0], so rotate_vec[N/2] is the - # bin corresponding to the [-1, 1, -1, 1, ...] time signal, which - # is both the maximum positive and negative frequency. - # I don't remember why we handle it differently. - rotate_vec[halflen] = np.cos(np.pi * tau) - - corr_sig = np.fft.ifft(rotate_vec * fft_sig) - - return -np.abs(np.sum(np.conj(corr_sig) * ref_sig)) - - optim_result = optimize.minimize_scalar(correlate_for_delay, bounds=(-1, 1), method='bounded', - options={'disp': True}) - - if optim_result.success: - best_tau = optim_result.x - - 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 = (plot_location + "/" + dt + "_tau.png") - plt.plot(ixs, taus) - plt.title("Subsample correlation, minimum is best: {}".format(best_tau)) - plt.savefig(tau_path) - plt.close() - - # Prepare rotate_vec = fft_sig with rotated phase - rotate_vec = np.exp(1j * best_tau * omega) - rotate_vec[halflen] = np.cos(np.pi * best_tau) - return np.fft.ifft(rotate_vec * fft_sig).astype(np.complex64) - else: - # print("Could not optimize: " + optim_result.message) - return np.zeros(0, dtype=np.complex64) - -def phase_align(sig, ref_sig, plot_location=None): - """Do phase alignment for sig relative to the reference signal - ref_sig. - - Returns the aligned signal""" - - angle_diff = (np.angle(sig) - np.angle(ref_sig)) % (2. * np.pi) - - real_diffs = np.cos(angle_diff) - imag_diffs = np.sin(angle_diff) - - if plot_location is not None: - dt = datetime.datetime.now().isoformat() - fig_path = plot_location + "/" + dt + "_phase_align.png" - - plt.subplot(511) - plt.hist(angle_diff, bins=60, label="Angle Diff") - plt.xlabel("Angle") - plt.ylabel("Count") - plt.legend(loc=4) - - plt.subplot(512) - plt.hist(real_diffs, bins=60, label="Real Diff") - plt.xlabel("Real Part") - plt.ylabel("Count") - plt.legend(loc=4) - - plt.subplot(513) - plt.hist(imag_diffs, bins=60, label="Imaginary Diff") - plt.xlabel("Imaginary Part") - plt.ylabel("Count") - plt.legend(loc=4) - - plt.subplot(514) - plt.plot(np.angle(ref_sig[:128]), label="ref_sig") - plt.plot(np.angle(sig[:128]), label="sig") - plt.xlabel("Angle") - plt.ylabel("Sample") - plt.legend(loc=4) - - real_diff = np.median(real_diffs) - imag_diff = np.median(imag_diffs) - - angle = np.angle(real_diff + 1j * imag_diff) - - #logging.debug( "Compensating phase by {} rad, {} degree. real median {}, imag median {}".format( angle, angle*180./np.pi, real_diff, imag_diff)) - sig = sig * np.exp(1j * -angle) - - if plot_location is not None: - plt.subplot(515) - plt.plot(np.angle(ref_sig[:128]), label="ref_sig") - plt.plot(np.angle(sig[:128]), label="sig") - plt.xlabel("Angle") - plt.ylabel("Sample") - plt.legend(loc=4) - plt.tight_layout() - plt.savefig(fig_path) - plt.close() - - return sig diff --git a/gui/dpd/Capture.py b/gui/dpd/Capture.py deleted file mode 100644 index 7d95f90..0000000 --- a/gui/dpd/Capture.py +++ /dev/null @@ -1,253 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine, Capture TX signal and RX feedback using ODR-DabMod's -# DPD Server. -# -# Copyright (c) 2017 Andreas Steger -# Copyright (c) 2018 Matthias P. Braendli -# -# http://www.opendigitalradio.org -# -# This file is part of ODR-DabMod. -# -# ODR-DabMod 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 of the -# License, or (at your option) any later version. -# -# ODR-DabMod 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 ODR-DabMod. If not, see . - -import socket -import struct -import os.path -import logging -import numpy as np -from scipy import signal -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -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 - correct phase offset - """ - - # Coarse sample-level alignment - c = np.abs(signal.correlate(sig_rx, sig_tx)) - off_meas = np.argmax(c) - sig_tx.shape[0] + 1 - off = int(abs(off_meas)) - - if off_meas > 0: - sig_tx = sig_tx[:-off] - sig_rx = sig_rx[off:] - elif off_meas < 0: - sig_tx = sig_tx[off:] - sig_rx = sig_rx[:-off] - - if off % 2 == 1: - sig_tx = sig_tx[:-1] - sig_rx = sig_rx[:-1] - - # 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, abs(off_meas) - -class Capture: - """Capture samples from ODR-DabMod""" - def __init__(self, samplerate, port, num_samples_to_request, plot_dir): - self.samplerate = samplerate - self.sizeof_sample = 8 # complex floats - self.port = port - self.num_samples_to_request = num_samples_to_request - self.plot_dir = plot_dir - - # Before we run the samples through the model, we want to accumulate - # them into bins depending on their amplitude, and keep only n_per_bin - # 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_n_bins = 64 # Number of bins between binning_start and binning_end - self.binning_n_per_bin = 128 # Number of measurements pre bin - - 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 - self.accumulated_bins = [np.zeros((0, 2), dtype=np.complex64) for i in range(self.binning_n_bins)] - - def _recv_exact(self, sock, num_bytes): - """Receive an exact number of bytes from a socket. This is - a wrapper around sock.recv() that can return less than the number - of requested bytes. - - Args: - sock (socket): Socket to receive data from. - num_bytes (int): Number of bytes that will be returned. - """ - bufs = [] - while num_bytes > 0: - b = sock.recv(num_bytes) - if len(b) == 0: - break - num_bytes -= len(b) - bufs.append(b) - return b''.join(bufs) - - def receive_tcp(self): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(4) - s.connect(('localhost', self.port)) - - logging.debug("Send version") - s.sendall(b"\x01") - - logging.debug("Send request for {} samples".format(self.num_samples_to_request)) - s.sendall(struct.pack("=I", self.num_samples_to_request)) - - logging.debug("Wait for TX metadata") - num_samps, tx_second, tx_pps = struct.unpack("=III", self._recv_exact(s, 12)) - tx_ts = tx_second + tx_pps / 16384000.0 - - if num_samps > 0: - logging.debug("Receiving {} TX samples".format(num_samps)) - txframe_bytes = self._recv_exact(s, num_samps * self.sizeof_sample) - txframe = np.fromstring(txframe_bytes, dtype=np.complex64) - else: - txframe = np.array([], dtype=np.complex64) - - logging.debug("Wait for RX metadata") - rx_second, rx_pps = struct.unpack("=II", self._recv_exact(s, 8)) - rx_ts = rx_second + rx_pps / 16384000.0 - - if num_samps > 0: - logging.debug("Receiving {} RX samples".format(num_samps)) - rxframe_bytes = self._recv_exact(s, num_samps * self.sizeof_sample) - rxframe = np.fromstring(rxframe_bytes, dtype=np.complex64) - else: - rxframe = np.array([], dtype=np.complex64) - - if logging.getLogger().getEffectiveLevel() == logging.DEBUG: - logging.debug('txframe: min {}, max {}, median {}'.format( - np.min(np.abs(txframe)), - np.max(np.abs(txframe)), - np.median(np.abs(txframe)))) - - logging.debug('rxframe: min {}, max {}, median {}'.format( - np.min(np.abs(rxframe)), - np.max(np.abs(rxframe)), - np.median(np.abs(rxframe)))) - - logging.debug("Disconnecting") - s.close() - - return txframe, tx_ts, rxframe, rx_ts - - def _plot_spectrum(self, signal, filename, title): - fig = plt.figure() - ax = plt.subplot(1, 1, 1) - - fft = np.fft.fftshift(np.fft.fft(signal)) - fft_db = 20 * np.log10(np.abs(fft)) - - ax.plot(fft_db) - ax.set_title(title) - fig.tight_layout() - fig.savefig(os.path.join(self.plot_dir, filename)) - plt.close(fig) - - 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 - - rxframe = rxframe * self.rx_normalisation - txframe_aligned, rxframe_aligned, coarse_offset = align_samples(txframe, rxframe) - - self._plot_spectrum(rxframe[:8192], "rxframe.png", "RX Frame") - self._plot_spectrum(txframe[:8192], "txframe.png", "RX Frame") - - return tx_ts, tx_median, rx_ts, rx_median, np.abs(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) - """ - - txframe, tx_ts, rxframe, rx_ts = self.receive_tcp() - - # 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] - - def pointcloud_png(self): - fig = plt.figure() - ax = plt.subplot(1, 1, 1) - for b in self.accumulated_bins: - if b: - ax.scatter( - np.abs(b[0]), - np.abs(b[1]), - s=0.1, - color="black") - ax.set_title("Captured and Binned Samples") - 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) - fig.tight_layout() - fig.savefig(os.path.join(self.plot_dir, "pointcloud.png")) - plt.close(fig) - - def _bin_and_accumulate(self, txframe, rxframe): - """Bin the samples and extend the accumulated samples""" - - bin_edges = np.linspace(self.binning_start, self.binning_end, self.binning_n_bins) - - minsize = self.num_samples_to_request - - for i, (tx_start, tx_end) in enumerate(zip(bin_edges, bin_edges[1:])): - txframe_abs = np.abs(txframe) - indices = np.bitwise_and(tx_start < txframe_abs, txframe_abs <= tx_end) - txsamples = np.asmatrix(txframe[indices]) - rxsamples = np.asmatrix(rxframe[indices]) - binned_sample_pairs = np.concatenate((txsamples, rxsamples)).T - - missing_in_bin = self.binning_n_per_bin - self.accumulated_bins[i].shape[0] - num_to_append = min(missing_in_bin, binned_sample_pairs.shape[0]) - print("Handling bin {} {}-{}, {} available, {} missing".format(i, tx_start, tx_end, binned_sample_pairs.shape[0], missing_in_bin)) - if num_to_append: - print("Appending {} to bin {} with shape {}".format(num_to_append, i, self.accumulated_bins[i].shape)) - - self.accumulated_bins[i] = np.concatenate((self.accumulated_bins[i], binned_sample_pairs[:num_to_append,...])) - print("{} now has shape {}".format(i, self.accumulated_bins[i].shape)) - diff --git a/gui/dpd/__init__.py b/gui/dpd/__init__.py deleted file mode 100644 index 9009436..0000000 --- a/gui/dpd/__init__.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -# -# DPD Computation Engine module -# -# Copyright (c) 2017 Andreas Steger -# Copyright (c) 2018 Matthias P. Braendli -# -# http://www.opendigitalradio.org -# -# This file is part of ODR-DabMod. -# -# ODR-DabMod 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 of the -# License, or (at your option) any later version. -# -# ODR-DabMod 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 ODR-DabMod. If not, see . - -from . import Capture -import numpy as np - -class DPD: - def __init__(self, plot_dir, samplerate=8192000): - self.samplerate = samplerate - - oversample = int(self.samplerate / 2048000) - self.T_F = oversample * 196608 # Transmission frame duration - self.T_NULL = oversample * 2656 # Null symbol duration - self.T_S = oversample * 2552 # Duration of OFDM symbols of indices l = 1, 2, 3,... L; - self.T_U = oversample * 2048 # Inverse of carrier spacing - self.T_C = oversample * 504 # Duration of cyclic prefix - - self.last_capture_info = {} - - port = 50055 - samples_to_capture = 81920 - self.capture = Capture.Capture(self.samplerate, port, samples_to_capture, plot_dir) - - def status(self): - r = {} - r['histogram'] = self.capture.bin_histogram() - r['capture'] = self.last_capture_info - return r - - 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['tx_median'] = "{:.2f}dB".format(20*np.log10(tx_median)) - result['rx_median'] = "{:.2f}dB".format(20*np.log10(rx_median)) - result['tx_ts'] = tx_ts - result['rx_ts'] = rx_ts - result['coarse_offset'] = int(coarse_offset) - result['correlation'] = float(correlation_coefficient) - return result - - def capture_samples(self): - """Captures samples and store them in the accumulated samples, - returns a dict with some info""" - result = {} - try: - txframe_aligned, tx_ts, tx_median, rxframe_aligned, rx_ts, rx_median = self.capture.get_samples() - result['status'] = "ok" - result['length'] = len(txframe_aligned) - result['tx_median'] = float(tx_median) - result['rx_median'] = float(rx_median) - result['tx_ts'] = tx_ts - result['rx_ts'] = rx_ts - except ValueError as e: - result['status'] = "Capture failed: {}".format(e) - - self.last_capture_info = result - - # tx, rx, phase_diff, n_per_bin = extStat.extract(txframe_aligned, rxframe_aligned) - # off = SA.calc_offset(txframe_aligned) - # print("off {}".format(off)) - # tx_mer = MER.calc_mer(txframe_aligned[off:off + c.T_U], debug_name='TX') - # print("tx_mer {}".format(tx_mer)) - # rx_mer = MER.calc_mer(rxframe_aligned[off:off + c.T_U], debug_name='RX') - # print("rx_mer {}".format(rx_mer)) - # mse = np.mean(np.abs((txframe_aligned - rxframe_aligned) ** 2)) - # print("mse {}".format(mse)) diff --git a/gui/logs/.keep b/gui/logs/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/gui/run.py b/gui/run.py deleted file mode 100755 index b83dd14..0000000 --- a/gui/run.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2018 -# Matthias P. Braendli, matthias.braendli@mpb.li -# -# http://www.opendigitalradio.org -# -# This file is part of ODR-DabMod. -# -# ODR-DabMod 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 of the -# License, or (at your option) any later version. -# -# ODR-DabMod 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 ODR-DabMod. If not, see . - -import configuration -from multiprocessing import Process, Pipe -import os.path -import cherrypy -import argparse -from jinja2 import Environment, FileSystemLoader -from api import API -import zmqrc -import dpd - -env = Environment(loader=FileSystemLoader('templates')) - -base_js = ["js/odr.js"] - -class Root: - def __init__(self, config_file, dpd_pipe): - self.config_file = config_file - self.conf = configuration.Configuration(self.config_file) - self.mod_rc = zmqrc.ModRemoteControl("localhost") - self.api = API(self.mod_rc, dpd_pipe) - - @cherrypy.expose - def index(self): - raise cherrypy.HTTPRedirect('/home') - - @cherrypy.expose - def about(self): - tmpl = env.get_template("about.html") - return tmpl.render(tab='about', js=base_js, is_login=False) - - @cherrypy.expose - def home(self): - tmpl = env.get_template("home.html") - return tmpl.render(tab='home', js=base_js, is_login=False) - - @cherrypy.expose - def rcvalues(self): - tmpl = env.get_template("rcvalues.html") - js = base_js + ["js/odr-rcvalues.js"] - return tmpl.render(tab='rcvalues', js=js, is_login=False) - - @cherrypy.expose - def modulator(self): - tmpl = env.get_template("modulator.html") - js = base_js + ["js/odr-modulator.js"] - return tmpl.render(tab='modulator', js=js, is_login=False) - - @cherrypy.expose - def predistortion(self): - tmpl = env.get_template("predistortion.html") - js = base_js + ["js/odr-predistortion.js"] - return tmpl.render(tab='predistortion', js=js, is_login=False) - -class DPDRunner: - def __init__(self, static_dir): - self.web_end, self.dpd_end = Pipe() - self.dpd = dpd.DPD(static_dir) - - def __enter__(self): - self.p = Process(target=self._handle_messages) - self.p.start() - return self.web_end - - def _handle_messages(self): - while True: - rx = self.dpd_end.recv() - if rx['cmd'] == "quit": - break - elif rx['cmd'] == "dpd-capture": - self.dpd.capture_samples() - elif rx['cmd'] == "dpd-calibrate": - self.dpd_end.send({'cmd': "dpd-calibration-result", - 'data': self.dpd.capture_calibration()}) - - - def __exit__(self, exc_type, exc_value, traceback): - self.web_end.send({'cmd': "quit"}) - self.p.join() - return False - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='ODR-DabMod Web GUI') - parser.add_argument('-c', '--config', - default="ui-config.json", - help='configuration filename') - cli_args = parser.parse_args() - - config = configuration.Configuration(cli_args.config) - if config.config is None: - print("Configuration file is missing or is not readable - {}".format(cli_args.config)) - sys.exit(1) - - if config.config['global']['daemon']: - cherrypy.process.plugins.Daemonizer(cherrypy.engine).subscribe() - - accesslog = os.path.realpath(os.path.join(config.config['global']['logs_directory'], 'access.log')) - errorlog = os.path.realpath(os.path.join(config.config['global']['logs_directory'], 'error.log')) - - cherrypy.config.update({ - 'engine.autoreload.on': True, - 'server.socket_host': config.config['global']['host'], - 'server.socket_port': int(config.config['global']['port']), - 'request.show_tracebacks' : True, - 'tools.sessions.on': False, - 'tools.encode.on': True, - 'tools.encode.encoding': "utf-8", - 'log.access_file': accesslog, - 'log.error_file': errorlog, - 'log.screen': True, - }) - - staticdir = os.path.realpath(config.config['global']['static_directory']) - - with DPDRunner(os.path.join(staticdir, "dpd")) as dpd_pipe: - cherrypy.tree.mount( - Root(cli_args.config, dpd_pipe), config={ - '/': { }, - '/dpd': { - 'tools.staticdir.on': True, - 'tools.staticdir.dir': os.path.join(staticdir, u"dpd/") - }, - '/css': { - 'tools.staticdir.on': True, - 'tools.staticdir.dir': os.path.join(staticdir, u"css/") - }, - '/js': { - 'tools.staticdir.on': True, - 'tools.staticdir.dir': os.path.join(staticdir, u"js/") - }, - '/fonts': { - 'tools.staticdir.on': True, - 'tools.staticdir.dir': os.path.join(staticdir, u"fonts/") - }, - '/favicon.ico': { - 'tools.staticfile.on': True, - 'tools.staticfile.filename': os.path.join(staticdir, u"fonts/favicon.ico") - }, - } - ) - - cherrypy.engine.start() - cherrypy.engine.block() - diff --git a/gui/static/css/bootstrap.min.css b/gui/static/css/bootstrap.min.css deleted file mode 100644 index 28f154d..0000000 --- a/gui/static/css/bootstrap.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Bootstrap v3.3.2 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none;visibility:hidden}.collapse.in{display:block;visibility:visible}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none;visibility:hidden}.tab-content>.active{display:block;visibility:visible}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important;visibility:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:absolute;top:0;right:0;left:0;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/gui/static/css/jquery.gritter.css b/gui/static/css/jquery.gritter.css deleted file mode 100755 index bd13a9c..0000000 --- a/gui/static/css/jquery.gritter.css +++ /dev/null @@ -1,100 +0,0 @@ -/* the norm */ -#gritter-notice-wrapper { - position:fixed; - top:60px; - right:15px; - width:301px; - z-index:9999; -} -#gritter-notice-wrapper.top-left { - left: 20px; - right: auto; -} -#gritter-notice-wrapper.bottom-right { - top: auto; - left: auto; - bottom: 20px; - right: 20px; -} -#gritter-notice-wrapper.bottom-left { - top: auto; - right: auto; - bottom: 20px; - left: 20px; -} -.gritter-item-wrapper { - position:relative; - margin:0 0 10px 0; - background:url('/fonts/ie-spacer.gif'); /* ie7/8 fix */ -} -.gritter-top { - background:url(/fonts/gritter.png) no-repeat left -30px; - height:10px; -} -.hover .gritter-top { - background-position:right -30px; -} -.gritter-bottom { - background:url(/fonts/gritter.png) no-repeat left bottom; - height:8px; - margin:0; -} -.hover .gritter-bottom { - background-position: bottom right; -} -.gritter-item { - display:block; - background:url(/fonts/gritter.png) no-repeat left -40px; - color:#eee; - padding:2px 11px 8px 11px; - font-size: 11px; - font-family:verdana; -} -.hover .gritter-item { - background-position:right -40px; -} -.gritter-item p { - padding:0; - margin:0; -} -.gritter-close { - display:none; - position:absolute; - top:5px; - left:3px; - background:url(/fonts/gritter.png) no-repeat left top; - cursor:pointer; - width:30px; - height:30px; -} -.gritter-title { - font-size:14px; - font-weight:bold; - padding:0 0 7px 0; - display:block; - text-shadow:1px 1px 0 #000; /* Not supported by IE :( */ -} -.gritter-image { - width:48px; - height:48px; - float:left; -} -.gritter-with-image, -.gritter-without-image { - padding:0 0 5px 0; -} -.gritter-with-image { - width:220px; - float:right; -} -/* for the light (white) version of the gritter notice */ -.gritter-light .gritter-item, -.gritter-light .gritter-bottom, -.gritter-light .gritter-top, -.gritter-close { - background-image: url(/fonts/gritter-light.png); - color: #222; -} -.gritter-light .gritter-title { - text-shadow: none; -} diff --git a/gui/static/dpd/index.txt b/gui/static/dpd/index.txt deleted file mode 100644 index f8b8874..0000000 --- a/gui/static/dpd/index.txt +++ /dev/null @@ -1 +0,0 @@ -This folder contains plots generated by the DPDCE diff --git a/gui/static/fonts/accept.png b/gui/static/fonts/accept.png deleted file mode 100644 index 0a2b4f3..0000000 Binary files a/gui/static/fonts/accept.png and /dev/null differ diff --git a/gui/static/fonts/favicon.ico b/gui/static/fonts/favicon.ico deleted file mode 100644 index 29817c8..0000000 Binary files a/gui/static/fonts/favicon.ico and /dev/null differ diff --git a/gui/static/fonts/glyphicons-halflings-regular.eot b/gui/static/fonts/glyphicons-halflings-regular.eot deleted file mode 100644 index b93a495..0000000 Binary files a/gui/static/fonts/glyphicons-halflings-regular.eot and /dev/null differ diff --git a/gui/static/fonts/glyphicons-halflings-regular.svg b/gui/static/fonts/glyphicons-halflings-regular.svg deleted file mode 100644 index 94fb549..0000000 --- a/gui/static/fonts/glyphicons-halflings-regular.svg +++ /dev/null @@ -1,288 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/gui/static/fonts/glyphicons-halflings-regular.ttf b/gui/static/fonts/glyphicons-halflings-regular.ttf deleted file mode 100644 index 1413fc6..0000000 Binary files a/gui/static/fonts/glyphicons-halflings-regular.ttf and /dev/null differ diff --git a/gui/static/fonts/glyphicons-halflings-regular.woff b/gui/static/fonts/glyphicons-halflings-regular.woff deleted file mode 100644 index 9e61285..0000000 Binary files a/gui/static/fonts/glyphicons-halflings-regular.woff and /dev/null differ diff --git a/gui/static/fonts/glyphicons-halflings-regular.woff2 b/gui/static/fonts/glyphicons-halflings-regular.woff2 deleted file mode 100644 index 64539b5..0000000 Binary files a/gui/static/fonts/glyphicons-halflings-regular.woff2 and /dev/null differ diff --git a/gui/static/fonts/gritter-light.png b/gui/static/fonts/gritter-light.png deleted file mode 100644 index 6b5626b..0000000 Binary files a/gui/static/fonts/gritter-light.png and /dev/null differ diff --git a/gui/static/fonts/gritter.png b/gui/static/fonts/gritter.png deleted file mode 100644 index 0ca3bc0..0000000 Binary files a/gui/static/fonts/gritter.png and /dev/null differ diff --git a/gui/static/fonts/ie-spacer.gif b/gui/static/fonts/ie-spacer.gif deleted file mode 100644 index 5bfd67a..0000000 Binary files a/gui/static/fonts/ie-spacer.gif and /dev/null differ diff --git a/gui/static/fonts/warning.png b/gui/static/fonts/warning.png deleted file mode 100644 index 01ad7d0..0000000 Binary files a/gui/static/fonts/warning.png and /dev/null differ diff --git a/gui/static/js/bootstrap.min.js b/gui/static/js/bootstrap.min.js deleted file mode 100644 index c6d3692..0000000 --- a/gui/static/js/bootstrap.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v3.3.2 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ -if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.2",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.2",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.2",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.2",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.2",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('