Eval supports model for float32 data type accepted/tizen/unified/20211029.132535 submit/tizen/20211028.105706
authorJuyeon Lee <juyeonne.lee@samsung.com>
Thu, 28 Oct 2021 02:37:37 +0000 (11:37 +0900)
committerYoungjae Shin <yj99.shin@samsung.com>
Thu, 28 Oct 2021 10:55:27 +0000 (19:55 +0900)
* Eval supports model for float32 data type

[Problem]
1. after input tensor info applied input configuration,
could copy data as much as configured.
2. it is necessary to check input configed pipeline with various model

[Solution]
added eval opt model_dtype to verify model in uint8 and float32

Signed-off-by: Juyeon Lee <juyeonne.lee@samsung.com>
* fix labels size

* convert uint8 img to float32

* change opt order

Change-Id: I7b35ca82f656a53bb0dff0607c6bd2fe546ef975

tools/evaluation/tasks/inference_peer/generic/image_classification.cc
tools/evaluation/tasks/inference_peer/tizen.native/image_classification.cc

index a17eedf..15afa85 100644 (file)
@@ -65,16 +65,19 @@ constexpr char EdgePortOpt[] = "port";
 
 // for device
 constexpr char DeviceModelFileOpt[] = "model_file";
+constexpr char DeviceModelDataTypeOpt[] = "model_dtype";
 constexpr char DeviceImagePathOpt[] = "image_path";
 constexpr char DeviceOutputLabelsOpt[] = "output_labels";
 constexpr char DeviceReqIPOpt[] = "edge_ip";
 constexpr char DeviceReqPortOpt[] = "req_port";
 constexpr char DeviceRepPortOpt[] = "rep_port";
 
-// mobilenet v1
+// Imagenet(ILSVRC-2012-CLS) classification with MobileNet V1
 const int MOBILENETV1_HEIGHT = 224;
 const int MOBILENETV1_WIDTH = 224;
 const int OUTPUT_LABELS_NUM = 1001;
+const int UINT8_BYTES = 1;
+const int FLOAT32_BYTES = 4;
 
 const int IMG_HEIGHT = 100;
 const int IMG_WIDTH = 100;
@@ -91,11 +94,13 @@ public:
     ImageClassification()
         : edge_ip_("127.0.0.1")
         , peer_name_(BEYOND_PLUGIN_PEER_NN_NAME)
+        , model_dtype_("uint8")
         , req_port_(3000)
         , rep_port_(3001)
         , service_port_(3000)
         , use_config_(0)
         , discovered_(0)
+        , num_bytes_per_channel_(UINT8_BYTES)
         , target_(TargetMode::UNKNOWN)
         , session_h_(nullptr)
         , evt_h_(nullptr)
@@ -162,11 +167,13 @@ private:
     std::string output_label_path_;
     std::string edge_ip_;
     std::string peer_name_;
+    std::string model_dtype_;
     unsigned short req_port_;
     unsigned short rep_port_;
     unsigned short service_port_;
     int use_config_;
     int discovered_;
+    int num_bytes_per_channel_;
 
     TargetMode target_;
     std::vector<std::string> image_labels_;
@@ -186,7 +193,9 @@ std::vector<Option> ImageClassification::GetOptions()
         Option::CreateOption(UseConfigOpt, &use_config_,
                              "Peer configuration On:1, Off:0 (default: 0)\n"),
         Option::CreateOption(DeviceModelFileOpt, &model_file_path_,
-                             "Path to tflite model file"),
+                             "Path to tflite model file(Imagenet classification with MobileNet V1)"),
+        Option::CreateOption(DeviceModelDataTypeOpt, &model_dtype_,
+                             "Model data type(`uint8` or `float32` default: uint8)"),
         Option::CreateOption(DeviceImagePathOpt, &input_image_path_,
                              "Path to input image file that will be evaluated"),
         Option::CreateOption(DeviceOutputLabelsOpt, &output_label_path_,
@@ -285,10 +294,18 @@ bool ImageClassification::CheckCmdline()
         if (LoadLabels() == false) {
             printf("Failed to load labels\n");
         }
+        if (model_dtype_.compare("float32") == 0) {
+            num_bytes_per_channel_ = FLOAT32_BYTES;
+        } else if (model_dtype_.compare("uint8") == 0) {
+            num_bytes_per_channel_ = UINT8_BYTES;
+        } else {
+            printf("Unsupported data type : %s\n", model_dtype_.c_str());
+            result = false;
+        }
         if (result == true) {
             printf("------------------------------\n");
             printf("Target : %s \n", run_as_.c_str());
-            printf("Model File : %s \n", model_file_path_.c_str());
+            printf("Model : %s (data type %s)\n", model_file_path_.c_str(), model_dtype_.c_str());
             printf("Input Image File : %s \n", input_image_path_.c_str());
             printf("Output Label File : %s \n", output_label_path_.c_str());
             printf("Edge IP Address : %s \n", edge_ip_.c_str());
@@ -369,6 +386,15 @@ bool ImageClassification::CheckDevice_InferencePeer()
     }
 
     if (use_config_ == 1) {
+        const char *trans_mode;
+        const char *trans_opt;
+        if (num_bytes_per_channel_ == 4) {
+            trans_mode = "arithmetic";
+            trans_opt = "typecast:float32,add:-127.5,div:127.5";
+        } else {
+            trans_mode = "typecast";
+            trans_opt = "uint8";
+        }
         struct beyond_input_image_config image_config = {
             .format = "BGR",
             .width = IMG_WIDTH,
@@ -376,8 +402,8 @@ bool ImageClassification::CheckDevice_InferencePeer()
             .convert_format = "RGB",
             .convert_width = MOBILENETV1_WIDTH,
             .convert_height = MOBILENETV1_HEIGHT,
-            .transform_mode = "typecast",
-            .transform_option = "uint8"
+            .transform_mode = trans_mode,
+            .transform_option = trans_opt
         };
         struct beyond_input_config input_config;
         input_config.input_type = BEYOND_INPUT_TYPE_IMAGE;
@@ -480,6 +506,13 @@ bool ImageClassification::LoadLabels()
     while (std::getline(stream, line)) {
         image_labels_.push_back(line);
     }
+
+    int loaded = image_labels_.size();
+    if (OUTPUT_LABELS_NUM != loaded) {
+        printf("Failed to get label from output(loaded %d)\n", loaded);
+        return false;
+    }
+
     return true;
 }
 
@@ -518,16 +551,6 @@ bool ImageClassification::CheckEdge_Peer()
         return false;
     }
 
-    if (use_config_ == 1) {
-        // NO implementation yet for service config
-        struct beyond_config config = { 's', nullptr };
-        ret = beyond_peer_configure(peer_h_, &config);
-        if (ret < 0) {
-            printf("Failed to config input : %d\n", ret);
-            return false;
-        }
-    }
-
     struct beyond_peer_info info = {
         .name = const_cast<char *>("name"),
         .host = const_cast<char *>("0.0.0.0"),
@@ -647,15 +670,20 @@ bool ImageClassification::RunMobilenet()
         cv::resize(inputImage, resizedImage,
                    cv::Size(IMG_WIDTH, IMG_HEIGHT));
     } else {
-        cv::resize(inputImage, resizedImage,
-                   cv::Size(MOBILENETV1_WIDTH, MOBILENETV1_HEIGHT));
-        cv::cvtColor(resizedImage, resizedImage, cv::COLOR_BGR2RGB);
+        if (num_bytes_per_channel_ == FLOAT32_BYTES) {
+            cv::Mat temp;
+            cv::resize(inputImage, temp,
+                       cv::Size(MOBILENETV1_WIDTH, MOBILENETV1_HEIGHT));
+            cv::cvtColor(temp, temp, cv::COLOR_BGR2RGB);
+            temp.convertTo(resizedImage, CV_32FC3, 1/255.0);
+        }
+        else {
+            cv::resize(inputImage, resizedImage,
+                       cv::Size(MOBILENETV1_WIDTH, MOBILENETV1_HEIGHT));
+            cv::cvtColor(resizedImage, resizedImage, cv::COLOR_BGR2RGB);
+        }
     }
 
-    int size_in_byte = resizedImage.size().width * resizedImage.size().height * resizedImage.channels() * sizeof(unsigned char);
-
-    // configure tensor_info, tensor (by user for a tempoerary) ?
-    // input tensor
     const struct beyond_tensor_info *input_info;
     int num_inputs;
     if (beyond_inference_get_input_tensor_info(inference_h_, &input_info, &num_inputs)) {
@@ -671,9 +699,10 @@ bool ImageClassification::RunMobilenet()
 
     beyond_tensor *beyond_tensors = BEYOND_TENSOR(in_tensor_h);
     auto &in = beyond_tensors[0];
-    in.type = BEYOND_TENSOR_TYPE_UINT8;
-    in.size = size_in_byte;
-    memcpy(in.data, resizedImage.data, size_in_byte);
+    if (DEBUG == true) {
+        printf("model input (type : 0x%.2X, size : %d )\n", in.type, in.size);
+    }
+    memcpy(in.data, resizedImage.data, in.size);
 
     // invoke
     if (beyond_inference_do(inference_h_, in_tensor_h, nullptr) < 0) {
@@ -693,37 +722,68 @@ bool ImageClassification::RunMobilenet()
 bool ImageClassification::PrintResult()
 {
     beyond_tensor_h out_tensor_h;
-    int count; // dummy: Could not get count in this api beyond_inference_get_output
+    int count;
+    int index = -1;
+    int copy_bytes = OUTPUT_LABELS_NUM * num_bytes_per_channel_;
+
     if (beyond_inference_get_output(inference_h_, &out_tensor_h, &count) < 0) {
         printf("Failed beyond_inference_get_output\n");
         return false;
     }
+    if ((out_tensor_h == nullptr) || (count <= 0)) {
+        printf("Invalid out_tensor_h:%p, count: %d\n", out_tensor_h, count);
+        return false;
+    }
 
     beyond_tensor *tensors = BEYOND_TENSOR(out_tensor_h);
-    if ((tensors[0].type != BEYOND_TENSOR_TYPE_UINT8) ||
-        (tensors[0].size != OUTPUT_LABELS_NUM)) {
-        printf("incorrect output( type : %d, size : %d)\n", tensors[0].type, tensors[0].size);
-        printf("expected result( type : %d, size : %d)\n", BEYOND_TENSOR_TYPE_UINT8, OUTPUT_LABELS_NUM);
+    if (DEBUG == true) {
+        printf("model output (type : 0x%.2X, size : %d )\n", tensors[0].type, tensors[0].size);
     }
 
-    uint8_t max_score = 0;
-    uint8_t scores[OUTPUT_LABELS_NUM];
-    int index = -1;
-    int loaded = image_labels_.size();
+    if (tensors[0].size != copy_bytes) {
+        printf("incorrect size( %d, but expected : %d)\n", tensors[0].size, copy_bytes);
+        return false;
+    }
 
-    memcpy(&scores, tensors[0].data, tensors[0].size);
+    if (num_bytes_per_channel_ == UINT8_BYTES) {
+        if (tensors[0].type != BEYOND_TENSOR_TYPE_UINT8) {
+            printf("incorrect type( 0x%.2X, but expected : %d )\n", tensors[0].type, BEYOND_TENSOR_TYPE_UINT8);
+            return false;
+        }
+        uint8_t max_score = 0;
+        uint8_t scores[OUTPUT_LABELS_NUM];
 
-    for (int i = 0; i < tensors[0].size; i++) {
-        if (scores[i] > 0 && scores[i] > max_score) {
-            index = i;
-            max_score = scores[i];
+        memcpy(&scores, tensors[0].data, copy_bytes);
+
+        for (int i = 0; i < OUTPUT_LABELS_NUM; i++) {
+            if (scores[i] > 0 && scores[i] > max_score) {
+                index = i;
+                max_score = scores[i];
+            }
         }
+    } else if (num_bytes_per_channel_ == FLOAT32_BYTES) {
+        if (tensors[0].type != BEYOND_TENSOR_TYPE_FLOAT32) {
+            printf("incorrect type( %d, but expected : %d )\n", tensors[0].type, BEYOND_TENSOR_TYPE_FLOAT32);
+            return false;
+        }
+        float max_score = 0;
+        float scores[OUTPUT_LABELS_NUM];
+
+        memcpy(&scores, tensors[0].data, tensors[0].size);
+
+        for (int i = 0; i < OUTPUT_LABELS_NUM; i++) {
+            if (scores[i] > 0 && scores[i] > max_score) {
+                index = i;
+                max_score = scores[i];
+            }
+        }
+    } else {
+        printf("Unsupported type( type : %d )\n", tensors[0].type);
     }
 
     beyond_inference_unref_tensor(out_tensor_h);
 
-    if ((index == -1) ||
-        (loaded < index)) {
+    if (index == -1) {
         printf("Failed to get label from output\n");
         return false;
     }
index 0bc86f2..5404481 100644 (file)
@@ -41,6 +41,7 @@ constexpr char EdgePortOpt[] = "port";
 
 // for device
 constexpr char DeviceModelFileOpt[] = "model_file";
+constexpr char DeviceModelDataTypeOpt[] = "model_dtype";
 constexpr char DeviceImagePathOpt[] = "image_path";
 constexpr char DeviceOutputLabelsOpt[] = "output_labels";
 constexpr char DeviceReqIPOpt[] = "edge_ip";
@@ -50,10 +51,12 @@ constexpr char DeviceInvocationOpt[] = "invoke";
 constexpr char DeviceInputFPSOpt[] = "input_fps";
 constexpr char DeviceRepeatOpt[] = "repeat";
 
-// mobilenet v1
+// Imagenet(ILSVRC-2012-CLS) classification with MobileNet V1
 const int MOBILENETV1_HEIGHT = 224;
 const int MOBILENETV1_WIDTH = 224;
 const int OUTPUT_LABELS_NUM = 1001;
+const int UINT8_BYTES = 1;
+const int FLOAT32_BYTES = 4;
 
 const int IMG_HEIGHT = 100;
 const int IMG_WIDTH = 100;
@@ -75,6 +78,7 @@ public:
     ImageClassification()
         : edge_ip_("127.0.0.1")
         , peer_name_(BEYOND_PLUGIN_PEER_NN_NAME)
+        , model_dtype_("uint8")
         , req_port_(3000)
         , rep_port_(3001)
         , service_port_(3000)
@@ -83,6 +87,7 @@ public:
         , repeat_(1)
         , num_invoked_(0)
         , discovered_(0)
+        , num_bytes_per_channel_(UINT8_BYTES)
         , main_loop_(g_main_loop_new(nullptr, FALSE))
         , target_(TargetMode::UNKNOWN)
         , invoke_(InvokeMode::SYNC)
@@ -144,6 +149,7 @@ private:
     std::string edge_ip_;
     std::string peer_name_;
     std::string invocation_; /* invocation mode in sync or async */
+    std::string model_dtype_;
     unsigned short req_port_;
     unsigned short rep_port_;
     unsigned short service_port_;
@@ -152,6 +158,7 @@ private:
     int repeat_;
     int num_invoked_;
     int discovered_;
+    int num_bytes_per_channel_;
 
     GMainLoop *main_loop_;
     TargetMode target_;
@@ -171,7 +178,9 @@ std::vector<Option> ImageClassification::GetOptions()
         Option::CreateOption(UseConfigOpt, &use_config_,
                              "Peer configuration On:1, Off:0 (default: 0)\n"),
         Option::CreateOption(DeviceModelFileOpt, &model_file_path_,
-                             "Path to tflite model file"),
+                             "Path to tflite model file(Imagenet classification with MobileNet V1)"),
+        Option::CreateOption(DeviceModelDataTypeOpt, &model_dtype_,
+                             "Model data type(`uint8` or `float32` default: uint8)"),
         Option::CreateOption(DeviceImagePathOpt, &input_image_path_,
                              "Path to input image file that will be evaluated"),
         Option::CreateOption(DeviceOutputLabelsOpt, &output_label_path_,
@@ -284,6 +293,15 @@ bool ImageClassification::CheckCmdline()
             invoke_ = InvokeMode::SYNC;
         }
 
+        if (model_dtype_.compare("float32") == 0) {
+            num_bytes_per_channel_ = FLOAT32_BYTES;
+        } else if (model_dtype_.compare("uint8") == 0) {
+            num_bytes_per_channel_ = UINT8_BYTES;
+        } else {
+            printf("Unsupported data type : %s\n", model_dtype_.c_str());
+            result = false;
+        }
+
         if ((input_fps_ < 1) || (input_fps_ > 60)) {
             printf("Out of range(1 ~ 60) of input fps : %d is set to default(10)\n", input_fps_);
             input_fps_ = 10;
@@ -292,7 +310,7 @@ bool ImageClassification::CheckCmdline()
         if (result == true) {
             printf("------------------------------\n");
             printf("Target : %s \n", run_as_.c_str());
-            printf("Model File : %s \n", model_file_path_.c_str());
+            printf("Model : %s (data type %s)\n", model_file_path_.c_str(), model_dtype_.c_str());
             printf("Input Image File : %s \n", input_image_path_.c_str());
             printf("Output Label File : %s \n", output_label_path_.c_str());
             printf("Edge IP Address : %s \n", edge_ip_.c_str());
@@ -358,6 +376,15 @@ bool ImageClassification::CheckDevice_InferencePeer()
     }
 
     if (use_config_ == 1) {
+        const char *trans_mode;
+        const char *trans_opt;
+        if (num_bytes_per_channel_ == 4) {
+            trans_mode = "arithmetic";
+            trans_opt = "typecast:float32,add:-127.5,div:127.5";
+        } else {
+            trans_mode = "typecast";
+            trans_opt = "uint8";
+        }
         struct beyond_input_image_config image_config = {
             .format = "BGR",
             .width = IMG_WIDTH,
@@ -365,8 +392,8 @@ bool ImageClassification::CheckDevice_InferencePeer()
             .convert_format = "RGB",
             .convert_width = MOBILENETV1_WIDTH,
             .convert_height = MOBILENETV1_HEIGHT,
-            .transform_mode = "typecast",
-            .transform_option = "uint8"
+            .transform_mode = trans_mode,
+            .transform_option = trans_opt
         };
         struct beyond_input_config input_config;
         input_config.input_type = BEYOND_INPUT_TYPE_IMAGE;
@@ -481,6 +508,13 @@ bool ImageClassification::LoadLabels()
     while (std::getline(stream, line)) {
         image_labels_.push_back(line);
     }
+
+    int loaded = image_labels_.size();
+    if (OUTPUT_LABELS_NUM != loaded) {
+        printf("Failed to get label from output(loaded %d)\n", loaded);
+        return false;
+    }
+
     return true;
 }
 
@@ -505,16 +539,6 @@ bool ImageClassification::CheckEdge_Peer()
         return false;
     }
 
-    if (use_config_ == 1) {
-        // NO implementation yet for service config
-        struct beyond_config config = { 's', nullptr };
-        ret = beyond_peer_configure(peer_h_, &config);
-        if (ret < 0) {
-            printf("Failed to config input : %d\n", ret);
-            return false;
-        }
-    }
-
     struct beyond_peer_info info = {
         .name = const_cast<char *>("name"),
         .host = const_cast<char *>("0.0.0.0"),
@@ -656,15 +680,20 @@ bool ImageClassification::RunMobilenet()
         cv::resize(inputImage, resizedImage,
                    cv::Size(IMG_WIDTH, IMG_HEIGHT));
     } else {
-        cv::resize(inputImage, resizedImage,
-                   cv::Size(MOBILENETV1_WIDTH, MOBILENETV1_HEIGHT));
-        cv::cvtColor(resizedImage, resizedImage, cv::COLOR_BGR2RGB);
+        if (num_bytes_per_channel_ == FLOAT32_BYTES) {
+            cv::Mat temp;
+            cv::resize(inputImage, temp,
+                       cv::Size(MOBILENETV1_WIDTH, MOBILENETV1_HEIGHT));
+            cv::cvtColor(temp, temp, cv::COLOR_BGR2RGB);
+            temp.convertTo(resizedImage, CV_32FC3, 1/255.0);
+        }
+        else {
+            cv::resize(inputImage, resizedImage,
+                       cv::Size(MOBILENETV1_WIDTH, MOBILENETV1_HEIGHT));
+            cv::cvtColor(resizedImage, resizedImage, cv::COLOR_BGR2RGB);
+        }
     }
 
-    int size_in_byte = resizedImage.size().width * resizedImage.size().height * resizedImage.channels() * sizeof(unsigned char);
-
-    // configure tensor_info, tensor (by user for a tempoerary) ?
-    // input tensor
     const struct beyond_tensor_info *input_info;
     int num_inputs;
     if (beyond_inference_get_input_tensor_info(inference_h_, &input_info, &num_inputs)) {
@@ -680,9 +709,10 @@ bool ImageClassification::RunMobilenet()
 
     beyond_tensor *beyond_tensors = BEYOND_TENSOR(in_tensor_h);
     auto &in = beyond_tensors[0];
-    in.type = BEYOND_TENSOR_TYPE_UINT8;
-    in.size = size_in_byte;
-    memcpy(in.data, resizedImage.data, size_in_byte);
+    if (DEBUG == true) {
+        printf("model input (type : 0x%.2X, size : %d )\n", in.type, in.size);
+    }
+    memcpy(in.data, resizedImage.data, in.size);
 
     // invoke
     num_invoked_++;
@@ -703,37 +733,68 @@ bool ImageClassification::RunMobilenet()
 bool ImageClassification::PrintResult()
 {
     beyond_tensor_h out_tensor_h;
-    int count; // dummy: Could not get count in this api beyond_inference_get_output
+    int count;
+    int index = -1;
+    int copy_bytes = OUTPUT_LABELS_NUM * num_bytes_per_channel_;
+
     if (beyond_inference_get_output(inference_h_, &out_tensor_h, &count) < 0) {
         printf("Failed beyond_inference_get_output\n");
         return false;
     }
+    if ((out_tensor_h == nullptr) || (count <= 0)) {
+        printf("Invalid out_tensor_h:%p, count: %d\n", out_tensor_h, count);
+        return false;
+    }
 
     beyond_tensor *tensors = BEYOND_TENSOR(out_tensor_h);
-    if ((tensors[0].type != BEYOND_TENSOR_TYPE_UINT8) ||
-        (tensors[0].size != OUTPUT_LABELS_NUM)) {
-        printf("incorrect output( type : %d, size : %d)\n", tensors[0].type, tensors[0].size);
-        printf("expected result( type : %d, size : %d)\n", BEYOND_TENSOR_TYPE_UINT8, OUTPUT_LABELS_NUM);
+    if (DEBUG == true) {
+        printf("model output (type : 0x%.2X, size : %d )\n", tensors[0].type, tensors[0].size);
     }
 
-    uint8_t max_score = 0;
-    uint8_t scores[OUTPUT_LABELS_NUM];
-    int index = -1;
-    int loaded = image_labels_.size();
+    if (tensors[0].size != copy_bytes) {
+        printf("incorrect size( %d, but expected : %d)\n", tensors[0].size, copy_bytes);
+        return false;
+    }
 
-    memcpy(&scores, tensors[0].data, tensors[0].size);
+    if (num_bytes_per_channel_ == UINT8_BYTES) {
+        if (tensors[0].type != BEYOND_TENSOR_TYPE_UINT8) {
+            printf("incorrect type( 0x%.2X, but expected : %d )\n", tensors[0].type, BEYOND_TENSOR_TYPE_UINT8);
+            return false;
+        }
+        uint8_t max_score = 0;
+        uint8_t scores[OUTPUT_LABELS_NUM];
+
+        memcpy(&scores, tensors[0].data, copy_bytes);
+
+        for (int i = 0; i < OUTPUT_LABELS_NUM; i++) {
+            if (scores[i] > 0 && scores[i] > max_score) {
+                index = i;
+                max_score = scores[i];
+            }
+        }
+    } else if (num_bytes_per_channel_ == FLOAT32_BYTES) {
+        if (tensors[0].type != BEYOND_TENSOR_TYPE_FLOAT32) {
+            printf("incorrect type( %d, but expected : %d )\n", tensors[0].type, BEYOND_TENSOR_TYPE_FLOAT32);
+            return false;
+        }
+        float max_score = 0;
+        float scores[OUTPUT_LABELS_NUM];
+
+        memcpy(&scores, tensors[0].data, tensors[0].size);
 
-    for (int i = 0; i < tensors[0].size; i++) {
-        if (scores[i] > 0 && scores[i] > max_score) {
-            index = i;
-            max_score = scores[i];
+        for (int i = 0; i < OUTPUT_LABELS_NUM; i++) {
+            if (scores[i] > 0 && scores[i] > max_score) {
+                index = i;
+                max_score = scores[i];
+            }
         }
+    } else {
+        printf("Unsupported type( type : %d )\n", tensors[0].type);
     }
 
     beyond_inference_unref_tensor(out_tensor_h);
 
-    if ((index == -1) ||
-        (loaded < index)) {
+    if (index == -1) {
         printf("Failed to get label from output\n");
         return false;
     }