File size: 12,645 Bytes
f77c97c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411

#include <sstream>
#include <fstream>
#include <vector>
#include <b64/encode.h>
#include <jsoncpp/json/json.h>
#include <opencv2/opencv.hpp>

// base64 to image
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>

/// Parameters used in the API
struct APIParams {
    /// A list of images, base64 encoded
    std::vector<std::string> data;

    /// The maximum number of keypoints to detect for each image
    std::vector<int> max_keypoints;

    /// The timestamps of the images
    std::vector<std::string> timestamps;

    /// Whether to convert the images to grayscale
    bool grayscale;

    /// The height and width of each image
    std::vector<std::vector<int>> image_hw;

    /// The type of feature detector to use
    int feature_type;

    /// The rotations of the images
    std::vector<double> rotates;

    /// The scales of the images
    std::vector<double> scales;

    /// The reference points of the images
    std::vector<std::vector<float>> reference_points;

    /// Whether to binarize the descriptors
    bool binarize;
};

/**
 * @brief Contains the results of a keypoint detector.
 *
 * @details Stores the keypoints and descriptors for each image.
 */
class KeyPointResults {
public:
    KeyPointResults() {}

    /**
     * @brief Constructor.
     *
     * @param kp The keypoints for each image.
     */
    KeyPointResults(const std::vector<std::vector<cv::KeyPoint>>& kp, 
                    const std::vector<cv::Mat>& desc)
        : keypoints(kp), descriptors(desc) {}

    /**
     * @brief Append keypoints to the result.
     *
     * @param kpts The keypoints to append.
     */
    inline void append_keypoints(std::vector<cv::KeyPoint>& kpts) {
        keypoints.emplace_back(kpts);
    }

    /**
     * @brief Append descriptors to the result.
     *
     * @param desc The descriptors to append.
     */
    inline void append_descriptors(cv::Mat& desc) {
        descriptors.emplace_back(desc);
    }

    /**
     * @brief Get the keypoints.
     *
     * @return The keypoints.
     */
    inline std::vector<std::vector<cv::KeyPoint>> get_keypoints() {
        return keypoints;
    }

    /**
     * @brief Get the descriptors.
     *
     * @return The descriptors.
     */
    inline std::vector<cv::Mat> get_descriptors() {
        return descriptors;
    }

private:
    std::vector<std::vector<cv::KeyPoint>> keypoints;
    std::vector<cv::Mat> descriptors;
    std::vector<std::vector<float>> scores;
};


/**
 * @brief Decodes a base64 encoded string.
 *
 * @param base64 The base64 encoded string to decode.
 * @return The decoded string.
 */
std::string base64_decode(const std::string& base64) {
    using namespace boost::archive::iterators;
    using It = transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>;

    // Find the position of the last non-whitespace character
    auto end = base64.find_last_not_of(" \t\n\r");
    if (end != std::string::npos) {
        // Move one past the last non-whitespace character
        end += 1;
    }

    // Decode the base64 string and return the result
    return std::string(It(base64.begin()), It(base64.begin() + end));
}



/**
 * @brief Decodes a base64 string into an OpenCV image
 *
 * @param base64 The base64 encoded string
 * @return The decoded OpenCV image
 */
cv::Mat base64_to_image(const std::string& base64) {
    // Decode the base64 string
    std::string decodedStr = base64_decode(base64);

    // Decode the image
    std::vector<uchar> data(decodedStr.begin(), decodedStr.end());
    cv::Mat img = cv::imdecode(data, cv::IMREAD_GRAYSCALE);

    // Check for errors
    if (img.empty()) {
        throw std::runtime_error("Failed to decode image");
    }

    return img;
}


/**
 * @brief Encodes an OpenCV image into a base64 string
 *
 * This function takes an OpenCV image and encodes it into a base64 string.
 * The image is first encoded as a PNG image, and then the resulting
 * bytes are encoded as a base64 string.
 *
 * @param img The OpenCV image
 * @return The base64 encoded string
 *
 * @throws std::runtime_error if the image is empty or encoding fails
 */
std::string image_to_base64(cv::Mat &img) {
    if (img.empty()) {
        throw std::runtime_error("Failed to read image");
    }

    // Encode the image as a PNG
    std::vector<uchar> buf;
    if (!cv::imencode(".png", img, buf)) {
        throw std::runtime_error("Failed to encode image");
    }

    // Encode the bytes as a base64 string
    using namespace boost::archive::iterators;
    using It = base64_from_binary<transform_width<std::vector<uchar>::const_iterator, 6, 8>>;
    std::string base64(It(buf.begin()), It(buf.end()));

    // Pad the string with '=' characters to a multiple of 4 bytes
    base64.append((3 - buf.size() % 3) % 3, '=');

    return base64;
}


/**
 * @brief Callback function for libcurl to write data to a string
 *
 * This function is used as a callback for libcurl to write data to a string.
 * It takes the contents, size, and nmemb as parameters, and writes the data to
 * the string.
 *
 * @param contents The data to write
 * @param size The size of the data
 * @param nmemb The number of members in the data
 * @param s The string to write the data to
 * @return The number of bytes written
 */
size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* s) {
    size_t newLength = size * nmemb;
    try {
        // Resize the string to fit the new data
        s->resize(s->size() + newLength);
    } catch (std::bad_alloc& e) {
        // If there's an error allocating memory, return 0
        return 0;
    }

    // Copy the data to the string
    std::copy(static_cast<const char*>(contents),
              static_cast<const char*>(contents) + newLength,
              s->begin() + s->size() - newLength);
    return newLength;
}

// Helper functions

/**
 * @brief Helper function to convert a type to a Json::Value
 *
 * This function takes a value of type T and converts it to a Json::Value.
 * It is used to simplify the process of converting a type to a Json::Value.
 *
 * @param val The value to convert
 * @return The converted Json::Value
 */
template <typename T>
Json::Value toJson(const T& val) {
    return Json::Value(val);
}

/**
 * @brief Converts a vector to a Json::Value
 *
 * This function takes a vector of type T and converts it to a Json::Value.
 * Each element in the vector is appended to the Json::Value array.
 *
 * @param vec The vector to convert to Json::Value
 * @return The Json::Value representing the vector
 */
template <typename T>
Json::Value vectorToJson(const std::vector<T>& vec) {
    Json::Value json(Json::arrayValue);
    for (const auto& item : vec) {
        json.append(item);
    }
    return json;
}

/**
 * @brief Converts a nested vector to a Json::Value
 *
 * This function takes a nested vector of type T and converts it to a Json::Value.
 * Each sub-vector is converted to a Json::Value array and appended to the main Json::Value array.
 *
 * @param vec The nested vector to convert to Json::Value
 * @return The Json::Value representing the nested vector
 */
template <typename T>
Json::Value nestedVectorToJson(const std::vector<std::vector<T>>& vec) {
    Json::Value json(Json::arrayValue);
    for (const auto& subVec : vec) {
        json.append(vectorToJson(subVec));
    }
    return json;
}



/**
 * @brief Converts the APIParams struct to a Json::Value
 *
 * This function takes an APIParams struct and converts it to a Json::Value.
 * The Json::Value is a JSON object with the following fields:
 * - data: a JSON array of base64 encoded images
 * - max_keypoints: a JSON array of integers, max number of keypoints for each image
 * - timestamps: a JSON array of timestamps, one for each image
 * - grayscale: a JSON boolean, whether to convert images to grayscale
 * - image_hw: a nested JSON array, each sub-array contains the height and width of an image
 * - feature_type: a JSON integer, the type of feature detector to use
 * - rotates: a JSON array of doubles, the rotation of each image
 * - scales: a JSON array of doubles, the scale of each image
 * - reference_points: a nested JSON array, each sub-array contains the reference points of an image
 * - binarize: a JSON boolean, whether to binarize the descriptors
 *
 * @param params The APIParams struct to convert
 * @return The Json::Value representing the APIParams struct
 */
Json::Value paramsToJson(const APIParams& params) {
    Json::Value json;
    json["data"] = vectorToJson(params.data);
    json["max_keypoints"] = vectorToJson(params.max_keypoints);
    json["timestamps"] = vectorToJson(params.timestamps);
    json["grayscale"] = toJson(params.grayscale);
    json["image_hw"] = nestedVectorToJson(params.image_hw);
    json["feature_type"] = toJson(params.feature_type);
    json["rotates"] = vectorToJson(params.rotates);
    json["scales"] = vectorToJson(params.scales);
    json["reference_points"] = nestedVectorToJson(params.reference_points);
    json["binarize"] = toJson(params.binarize);
    return json;
}

template<typename T>
cv::Mat jsonToMat(Json::Value json) {
    int rows = json.size();
    int cols = json[0].size();

    // Create a single array to hold all the data.
    std::vector<T> data;
    data.reserve(rows * cols);

    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            data.push_back(static_cast<T>(json[i][j].asInt()));
        }
    }

    // Create a cv::Mat object that points to the data.
    cv::Mat mat(rows, cols, CV_8UC1, data.data());  // Change the type if necessary.
    // cv::Mat mat(cols, rows,CV_8UC1, data.data());  // Change the type if necessary.

    return mat;
}



/**
 * @brief Decodes the response of the server and prints the keypoints
 *
 * This function takes the response of the server, a JSON string, and decodes
 * it. It then prints the keypoints and draws them on the original image.
 *
 * @param response The response of the server
 * @return The keypoints and descriptors
 */
KeyPointResults decode_response(const std::string& response, bool viz=true) {
    Json::CharReaderBuilder builder;
    Json::CharReader* reader = builder.newCharReader();

    Json::Value jsonData;
    std::string errors;

    // Parse the JSON response
    bool parsingSuccessful = reader->parse(response.c_str(),
        response.c_str() + response.size(), &jsonData, &errors);
    delete reader;

    if (!parsingSuccessful) {
        // Handle error
        std::cout << "Failed to parse the JSON, errors:" << std::endl;
        std::cout << errors << std::endl;
        return KeyPointResults();
    }

    KeyPointResults kpts_results;

    // Iterate over the images
    for (const auto& jsonItem : jsonData) {
        auto jkeypoints = jsonItem["keypoints"];
        auto jkeypoints_orig = jsonItem["keypoints_orig"];
        auto jdescriptors = jsonItem["descriptors"];
        auto jscores = jsonItem["scores"];
        auto jimageSize = jsonItem["image_size"];
        auto joriginalSize = jsonItem["original_size"];
        auto jsize = jsonItem["size"];

        std::vector<cv::KeyPoint> vkeypoints;
        std::vector<float> vscores;

        // Iterate over the keypoints
        int counter = 0;
        for (const auto& keypoint : jkeypoints_orig) {
            if (counter < 10) {
                // Print the first 10 keypoints
                std::cout << keypoint[0].asFloat() << ", "
                    << keypoint[1].asFloat() << std::endl;
            }
            counter++;
            // Convert the Json::Value to a cv::KeyPoint
            vkeypoints.emplace_back(cv::KeyPoint(keypoint[0].asFloat(),
                keypoint[1].asFloat(), 0.0));
        }

        if (viz && jsonItem.isMember("image_orig")) {

            auto jimg_orig = jsonItem["image_orig"];
            cv::Mat img = jsonToMat<uchar>(jimg_orig);
            cv::imwrite("viz_image_orig.jpg", img);

            // Draw keypoints on the image
            cv::Mat imgWithKeypoints;
            cv::drawKeypoints(img, vkeypoints, 
                imgWithKeypoints, cv::Scalar(0, 0, 255));

            // Write the image with keypoints
            std::string filename = "viz_image_orig_keypoints.jpg";
            cv::imwrite(filename, imgWithKeypoints);
        }

        // Iterate over the descriptors
        cv::Mat descriptors = jsonToMat<uchar>(jdescriptors); 
        kpts_results.append_keypoints(vkeypoints);
        kpts_results.append_descriptors(descriptors);
    }
    return kpts_results;
}