aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/cal/iq_cal.cpp
blob: f5640b01e94233af8f8c07cf6e37dece58765449 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
//
// Copyright 2020 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//

#include <uhd/cal/iq_cal.hpp>
#include <uhd/cal/iq_cal_generated.h>
#include <uhd/exception.hpp>
#include <uhd/utils/math.hpp>
#include <uhdlib/utils/interpolation.hpp>
#include <map>
#include <string>

using namespace uhd::usrp::cal;
using namespace uhd::math;

constexpr int VERSION_MAJOR = 1;
constexpr int VERSION_MINOR = 0;

/***********************************************************************
 * Helper routines
 **********************************************************************/
class iq_cal_impl : public iq_cal
{
public:
    iq_cal_impl(const std::string& name = "",
        const std::string& serial       = "",
        const uint64_t timestamp        = 0)
        : _name(name)
        , _serial(serial)
        , _timestamp(timestamp)
        , _interp(interp_mode::LINEAR)
    {
    }

    std::string get_name() const
    {
        return _name;
    }

    //! Return the name of this calibration table
    std::string get_serial() const
    {
        return _serial;
    }

    //! Timestamp of acquisition time
    uint64_t get_timestamp() const
    {
        return _timestamp;
    }

    void set_interp_mode(const interp_mode interp)
    {
        UHD_ASSERT_THROW(
            interp == interp_mode::LINEAR || interp == interp_mode::NEAREST_NEIGHBOR);
        _interp = interp;
    }

    std::complex<double> get_cal_coeff(const double freq) const
    {
        UHD_ASSERT_THROW(!_coeffs.empty());
        // Find the first coefficient in the map that maps to a larger frequency
        // than freq (or equal)
        auto next_coeff = _coeffs.lower_bound(freq);
        if (next_coeff == _coeffs.end()) {
            // This means freq is larger than our biggest key, and thus we
            // can't interpolate. We return the coeffs of the largest key.
            return _coeffs.rbegin()->second;
        }
        if (next_coeff == _coeffs.begin()) {
            // This means freq is smaller than our smallest key, and thus we
            // can't interpolate. We return the coeffs of the smallest key.
            return _coeffs.begin()->second;
        }
        // Stash away freqs and coeffs for easier code
        const auto hi_freq  = next_coeff->first;
        const auto hi_coeff = next_coeff->second;
        next_coeff--;
        const auto lo_coeff = next_coeff->second;
        const auto lo_freq  = next_coeff->first; // lo == low, not LO
        // Now, we're guaranteed to be between two points
        if (_interp == interp_mode::NEAREST_NEIGHBOR) {
            return (hi_freq - freq) < (freq - lo_freq) ? hi_coeff : lo_coeff;
        }
        using uhd::math::linear_interp;
        return std::complex<double>(
            linear_interp<double>(
                freq, lo_freq, lo_coeff.real(), hi_freq, hi_coeff.real()),
            linear_interp<double>(
                freq, lo_freq, lo_coeff.imag(), hi_freq, hi_coeff.imag()));
    }

    void set_cal_coeff(const double freq,
        const std::complex<double> coeff,
        const double suppression_abs   = 0,
        const double suppression_delta = 0)
    {
        _coeffs[freq] = coeff;
        _supp[freq]   = {suppression_abs, suppression_delta};
    }

    void clear()
    {
        _coeffs.clear();
        _supp.clear();
    }

    /**************************************************************************
     * Container API (Serialization/Deserialization)
     *************************************************************************/
    std::vector<uint8_t> serialize()
    {
        // This is a magic value to estimate the amount of space the builder will
        // have to reserve on top of the coeff data.
        // Worst case is we get this too low, and the builder will have to do a
        // single reallocation later.
        constexpr size_t RESERVE_HDR_BYTES = 20;
        const size_t initial_size_bytes =
            sizeof(IQCalCoeff) * _coeffs.size() + RESERVE_HDR_BYTES;
        flatbuffers::FlatBufferBuilder builder(initial_size_bytes);
        // Convert the coefficients to a vector of IQCalCoeff
        std::vector<IQCalCoeff> fb_coeffs;
        fb_coeffs.reserve(_coeffs.size());
        std::transform(_coeffs.cbegin(),
            _coeffs.cend(),
            std::back_inserter(fb_coeffs),
            [&](const coeffs_type::value_type& coeff) {
                const double freq     = coeff.first;
                const auto this_coeff = coeff.second;
                return IQCalCoeff(freq,
                    this_coeff.real(),
                    this_coeff.imag(),
                    _supp.count(freq) ? _supp.at(freq).first : 0.0,
                    _supp.count(freq) ? _supp.at(freq).second : 0.0);
            });
        // Now load it all into the FlatBuffer
        const auto metadata  = CreateMetadataDirect(builder,
            _name.c_str(),
            _serial.c_str(),
            _timestamp,
            VERSION_MAJOR,
            VERSION_MINOR);
        auto cal_table = CreateIQCalCoeffsDirect(builder, metadata, &fb_coeffs);
        FinishIQCalCoeffsBuffer(builder, cal_table);
        const size_t table_size = builder.GetSize();
        const uint8_t* table    = builder.GetBufferPointer();
        return std::vector<uint8_t>(table, table + table_size);
    }

    // This will amend the existing table. If that's not desired, then it is
    // necessary to call clear() ahead of time.
    void deserialize(const std::vector<uint8_t>& data)
    {
        auto verifier = flatbuffers::Verifier(data.data(), data.size());
        if (!VerifyIQCalCoeffsBuffer(verifier)) {
            throw uhd::runtime_error("iq_cal: Invalid data provided!");
        }
        auto cal_table = GetIQCalCoeffs(static_cast<const void*>(data.data()));
        // TODO we can handle this more nicely
        UHD_ASSERT_THROW(cal_table->metadata()->version_major() == VERSION_MAJOR);
        _name          = std::string(cal_table->metadata()->name()->c_str());
        _serial        = std::string(cal_table->metadata()->serial()->c_str());
        _timestamp     = cal_table->metadata()->timestamp();
        auto coeffs    = cal_table->coeffs();
        for (auto it = coeffs->begin(); it != coeffs->end(); ++it) {
            _coeffs[it->freq()] = {it->coeff_real(), it->coeff_imag()};
            // Suppression levels are really not necessary for runtime.
            // TODO: Come up with a way to skip this step when loading data for
            // runtime (and not future storage)
            _supp[it->freq()] = {it->suppression_abs(), it->suppression_delta()};
        }
    }


private:
    std::string _name;
    std::string _serial;
    uint64_t _timestamp;
    using coeffs_type = std::map<double, std::complex<double>>;
    coeffs_type _coeffs;
    // Abs suppression, delta suppression
    std::map<double, std::pair<double, double>> _supp;

    interp_mode _interp;
};


iq_cal::sptr iq_cal::make()
{
    return std::make_shared<iq_cal_impl>();
}

iq_cal::sptr iq_cal::make(
    const std::string& name, const std::string& serial, const uint64_t timestamp)
{
    return std::make_shared<iq_cal_impl>(name, serial, timestamp);
}