From 145e210a24c25dfb8110b4a463b5b238faae1ed6 Mon Sep 17 00:00:00 2001 From: HarshJ20 Date: Tue, 20 Jun 2023 11:42:04 +0530 Subject: [PATCH] [TEST] Test case for tensor_decoder::tensor_region - ssat based runTest.sh [incomplete] - getCropInfo.py for viewing the data produces of orange.unittest_plugins - gtest based test case in unittest_plugins with name TensorDecoder.TensorRegion [removed] - temporary file removed - Separate test unit for tensor_region - included missing Doxygen tags Signed-off-by: HarshJ20 --- tests/meson.build | 22 +++ .../nnstreamer_decoder_tensorRegion/getCropInfo.py | 76 +++++++ tests/nnstreamer_decoder_tensorRegion/runTest.sh | 34 ++++ .../unittest_tensorRegion.cc | 218 +++++++++++++++++++++ tests/nnstreamer_plugins/unittest_plugins.cc | 1 + 5 files changed, 351 insertions(+) create mode 100644 tests/nnstreamer_decoder_tensorRegion/getCropInfo.py create mode 100644 tests/nnstreamer_decoder_tensorRegion/runTest.sh create mode 100644 tests/nnstreamer_decoder_tensorRegion/unittest_tensorRegion.cc diff --git a/tests/meson.build b/tests/meson.build index 21df1fc..3a47c4b 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -84,6 +84,28 @@ if gtest_dep.found() test('unittest_sink', unittest_sink, timeout: 120, env: testenv) + # RUN unittest_tensorRegion + if tflite2_support_is_available + message('ssd_mobilenet_v2_coco.tflite model will be downloaded') + download_url = 'https://github.com/nnsuite/testcases/raw/master/DeepLearningModels/tensorflow-lite/ssd_mobilenet_v2_coco' + filename = 'ssd_mobilenet_v2_coco.tflite' + unittest_model = join_paths(unittest_base_dir, 'tests', 'test_models', 'models') + r = run_command('wget', '-nc', '-P', unittest_model, download_url + filename) + if r.returncode() == 0 + output = r.stdout().strip() + else + errortxt = r.stderr().strip() + endif + + unittest_tensorRegion = executable('unittest_tensorRegion', + join_paths('nnstreamer_decoder_tensorRegion', 'unittest_tensorRegion.cc'), + dependencies: [nnstreamer_unittest_deps], + install: get_option('install-test'), + install_dir: unittest_install_dir + ) + test('unittest_tensorRegion', unittest_tensorRegion, env: testenv) + endif + # Run unittest_plugins unittest_plugins = executable('unittest_plugins', join_paths('nnstreamer_plugins', 'unittest_plugins.cc'), diff --git a/tests/nnstreamer_decoder_tensorRegion/getCropInfo.py b/tests/nnstreamer_decoder_tensorRegion/getCropInfo.py new file mode 100644 index 0000000..1feff2f --- /dev/null +++ b/tests/nnstreamer_decoder_tensorRegion/getCropInfo.py @@ -0,0 +1,76 @@ +## +# SPDX-License-Identifier: LGPL-2.1-only +# +# Copyright (C) 2023 Harsh Jain +# +# @file getCropInfo.py +# @brief generate human readable output from out buffers +# @author Harsh Jain + +import sys +import gi +import struct + +gi.require_version('Gst', '1.0') +from gi.repository import Gst, GLib + +# Initialize GStreamer +Gst.init(None) + +# Check if the image path argument is provided +if len(sys.argv) < 2: + print("Please provide the path to the image file as an argument.") + sys.exit(1) + +image_path = sys.argv[1] + +# Create the GStreamer pipeline +pipeline_str = """ + filesrc location={} ! decodebin ! videoconvert ! videoscale ! video/x-raw,width=640,height=480,format=RGB ! videoscale ! video/x-raw,width=300,height=300,format=RGB ! tensor_converter ! tensor_transform mode=arithmetic option=typecast:float32,add:-127.5,div:127.5 ! tensor_filter framework=tensorflow2-lite model=ssd_mobilenet_v2_coco.tflite ! tensor_decoder mode=tensor_region option1=1 option2=../nnstreamer_decoder_boundingbox/coco_labels_list.txt option3=../nnstreamer_decoder_boundingbox/box_priors.txt ! appsink name=output +""".format(image_path) + +pipeline = Gst.parse_launch(pipeline_str) + +# Define callback function to process the tensor data + +def process_tensor_data(appsink): + sample = appsink.emit("pull-sample") + buffer = sample.get_buffer() + + # Extract tensor data + header_buffer = buffer.extract_dup(0, 128) + tensor_data_buffer = buffer.extract_dup(128, 16) # Extract the remaining data as the tensor + + # Process tensor data + tensor_size = len(tensor_data_buffer) // 4 # Assuming each value is a uint32 (4 bytes) + tensor_data = struct.unpack(f"{tensor_size}I", tensor_data_buffer) + + # Print the tensor data + print("Tensor data:", tensor_data) + + # Stop the main loop + loop.quit() + + return Gst.FlowReturn.OK + + + + +# Set up the appsink element to capture tensor data +output = pipeline.get_by_name("output") +output.set_property("emit-signals", True) +output.set_property("max-buffers", 1) +output.connect("new-sample", process_tensor_data) + +# Start the pipeline +pipeline.set_state(Gst.State.PLAYING) + +# Run the main loop +loop = GLib.MainLoop() +try: + loop.run() +except KeyboardInterrupt: + pass + +# Stop the pipeline and clean up +pipeline.set_state(Gst.State.NULL) diff --git a/tests/nnstreamer_decoder_tensorRegion/runTest.sh b/tests/nnstreamer_decoder_tensorRegion/runTest.sh new file mode 100644 index 0000000..193a254 --- /dev/null +++ b/tests/nnstreamer_decoder_tensorRegion/runTest.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +## +## SPDX-License-Identifier: LGPL-2.1-only +## +## @file runTest.sh +## @author Harsh Jain +## @date 18 JUNE, 2023 +## @brief SSAT Test Cases for NNStreamer +## +if [[ "$SSATAPILOADED" != "1" ]]; then + SILENT=0 + INDEPENDENT=1 + search="ssat-api.sh" + source $search + printf "${Blue}Independent Mode${NC}" +fi + +# This is compatible with SSAT (https://github.com/myungjoo/SSAT) +testInit $1 + +PATH_TO_PLUGIN="../../build" +PATH_TO_IMAGE="../test_models/data/orange.png" +PATH_TO_LABELS="../nnstreamer_decoder_boundingbox/coco_labels_list.txt" +PATH_TO_BOX_PRIORS="../nnstreamer_decoder_boundingbox/box_priors.txt" +PATH_TO_MODEL="../test_models/models/ssd_mobilenet_v2_coco.tflite" + + +gstTest "--gst-plugin-path=${PATH_TO_PLUGIN} filesrc location=${PATH_TO_IMAGE} ! pngdec ! videoconvert ! videoscale ! video/x-raw,width=300,height=300,format=RGB,framerate=0/1 ! tee name=t t. ! queue ! tensor_converter ! crop.raw t. ! queue ! tensor_converter ! tensor_transform mode=arithmetic option=typecast:float32,add:-127.5,div:127.5 ! tensor_filter framework=tensorflow2-lite model=${PATH_TO_MODEL} ! tensor_decoder mode=tensor_region option1=1 option2=${PATH_TO_LABELS} option3=${PATH_TO_BOX_PRIORS} ! crop.info tensor_crop name=crop ! other/tensors,format=flexible ! tensor_converter ! tensor_decoder mode=direct_video ! videoconvert ! videoscale ! video/x-raw,width=300,height=300,format=RGB ! pngenc ! filesink location=tensor_region_output_orange.png +" 0 0 0 $PERFORMANCE + + +callCompareTest tensor_region_orange.png tensor_region_output_orange.png 0 "mobilenet-ssd Decode 1" 0 +rm tensor_region_output_* +report diff --git a/tests/nnstreamer_decoder_tensorRegion/unittest_tensorRegion.cc b/tests/nnstreamer_decoder_tensorRegion/unittest_tensorRegion.cc new file mode 100644 index 0000000..18b7bdc --- /dev/null +++ b/tests/nnstreamer_decoder_tensorRegion/unittest_tensorRegion.cc @@ -0,0 +1,218 @@ +/** + * @file unittest_tensorRegion.cc + * @date 20 June 2023 + * @brief Unit test for tensor_decoder::tensor_region. (testcases to check data conversion or buffer transfer) + * @see https://github.com/nnstreamer/nnstreamer + * @author Harsh Jain + * @bug No known bugs. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../gst/nnstreamer/elements/gsttensor_sparseutil.h" +#include "../gst/nnstreamer/elements/gsttensor_transform.h" +#include "../unittest_util.h" + +#if defined(ENABLE_TENSORFLOW_LITE) || defined(ENABLE_TENSORFLOW2_LITE) +#define TEST_REQUIRE_TFLITE(Case, Name) TEST (Case, Name) +#else +#define TEST_REQUIRE_TFLITE(Case, Name) TEST (Case, DISABLED_##Name) +#endif + +/** + * @brief Call back function for tensor_region to parse outbuf + * + * @param sink The sink element + * @param user_data User data passed to the callback function + */ +static void new_data_cb(GstElement* sink, const gpointer user_data) +{ + GstSample* sample = nullptr; + + g_signal_emit_by_name(sink, "pull-sample", &sample); + + /** Expected values of cropping info for orange.png */ + guint32 expected_values[] = {58, 61, 219, 213}; + + if (sample != nullptr) { + GstBuffer* outbuf = gst_sample_get_buffer(sample); + GstMemory* mem = gst_buffer_peek_memory(outbuf, 0); + + if (mem != nullptr) { + GstMapInfo out_info; + + if (gst_memory_map(mem, &out_info, GST_MAP_READ)) { + GstTensorMetaInfo map; + int* data_ptr = nullptr; + + gst_tensor_meta_info_parse_header(&map, out_info.data); + + gsize hsize = gst_tensor_meta_info_get_header_size(&map); + gsize dsize = gst_tensor_meta_info_get_data_size(&map); + ASSERT_EQ(map.type, _NNS_UINT32); + + gsize esize = sizeof(guint32); + + ASSERT_EQ(hsize + dsize, out_info.size); + ASSERT_EQ((dsize % (esize * 4)), 0); + + data_ptr = reinterpret_cast(out_info.data + hsize); + + for (int i = 0; i < 4; i++) { + EXPECT_EQ(data_ptr[i], expected_values[i]); + } + + gst_memory_unmap(mem, &out_info); + } + } + + gst_sample_unref(sample); + } +} + +/** + * @brief Structure to hold information related to TensorRegion. + */ +struct TensorRegion { + GstElement* pipeline; /**< The pipeline element */ + GstElement* app_sink; /**< The app sink element */ + GMainLoop* main_loop; /**< The main loop */ +}; + + +/** + * @brief Callback function to handle pipeline messages. + * + * @param bus The GStreamer bus. + * @param message The GStreamer message. + * @param data Pointer to the TensorRegion structure. + * @return gboolean Returns TRUE to continue receiving messages. + */ +static gboolean on_pipeline_message(GstBus* bus, GstMessage* message, TensorRegion* data) +{ + switch (GST_MESSAGE_TYPE(message)) { + case GST_MESSAGE_EOS: + g_main_loop_quit(data->main_loop); + break; + case GST_MESSAGE_ERROR: { + g_print("Received error\n"); + + GError* err = NULL; + gchar* dbg_info = NULL; + + gst_message_parse_error(message, &err, &dbg_info); + g_printerr("ERROR from element %s: %s\n", GST_OBJECT_NAME(message->src), err->message); + g_printerr("Debugging info: %s\n", (dbg_info) ? dbg_info : "none"); + g_error_free(err); + g_free(dbg_info); + } + + g_main_loop_quit(data->main_loop); + break; + case GST_MESSAGE_STATE_CHANGED: + break; + default: + break; + } + + /** Return FALSE to stop receiving messages after the callback function + * has handled the current message. */ + return G_SOURCE_CONTINUE; +} + + +/** + * @brief Test for tensor_decoder::tensor_region + */ +TEST_REQUIRE_TFLITE (TensorDecoder, TensorRegion) +{ + GstBus* bus; + const gchar* root_path = g_getenv("NNSTREAMER_SOURCE_ROOT_PATH"); + if (root_path == nullptr) + root_path = ".."; + + const gchar* image_path = g_build_filename(root_path, "tests", "test_models", "data", + "orange.png", nullptr); + const gchar* model = g_build_filename(root_path, "tests", "test_models", "models", "ssd_mobilenet_v2_coco.tflite", nullptr); + const gchar* labels_path = g_build_filename(root_path, "tests", "test_models", "labels", "labels.txt", nullptr); + const gchar* box_priors_path = g_build_filename(root_path, "tests", "nnstreamer_decoder_boundingbox", "box_priors.txt", nullptr); + + ASSERT_TRUE(g_file_test(image_path, G_FILE_TEST_EXISTS)); + ASSERT_TRUE(g_file_test(model, G_FILE_TEST_EXISTS)); + ASSERT_TRUE(g_file_test(labels_path, G_FILE_TEST_EXISTS)); + ASSERT_TRUE(g_file_test(box_priors_path, G_FILE_TEST_EXISTS)); + + /** Create the GStreamer pipeline */ + gchar* pipeline_str = g_strdup_printf( + "filesrc location=%s ! decodebin ! videoconvert ! videoscale ! " + "video/x-raw,width=640,height=480,format=RGB ! videoscale ! " + "video/x-raw,width=300,height=300,format=RGB ! tensor_converter ! " + "tensor_transform mode=arithmetic option=typecast:float32,add:-127.5,div:127.5 ! " + "tensor_filter framework=tensorflow2-lite model=%s ! " + "tensor_decoder mode=tensor_region option1=1 option2=%s option3=%s ! " + "appsink name=sinkx", + image_path, model, labels_path, box_priors_path); + + GstElement* pipeline = gst_parse_launch(pipeline_str, nullptr); + g_free(pipeline_str); + + GstElement* app_sink = gst_bin_get_by_name(GST_BIN(pipeline), "sinkx"); + + /** Create the TensorRegion structure and assign pipeline and app_sink */ + TensorRegion data; + data.pipeline = pipeline; + data.app_sink = app_sink; + bus = gst_element_get_bus(data.pipeline); + gst_bus_add_watch(bus, (GstBusFunc)on_pipeline_message, &data); + gst_object_unref(bus); + + /** Enable signal emission from the app_sink */ + g_object_set(app_sink, "emit-signals", TRUE, NULL); + + /** Connect the new-sample callback to the app_sink */ + g_signal_connect(app_sink, "new-sample", G_CALLBACK(new_data_cb), nullptr); + + /** Start playing the pipeline */ + gst_element_set_state(pipeline, GST_STATE_PLAYING); + + /** Create a GLib Main Loop and set it to run */ + data.main_loop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(data.main_loop); + + /** Free resources */ + gst_element_set_state(pipeline, GST_STATE_NULL); + gst_object_unref(pipeline); +} + + +/** + * @brief Main function for unit test. + */ +int +main (int argc, char **argv) +{ + int ret = -1; + try { + testing::InitGoogleTest (&argc, argv); + } catch (...) { + g_warning ("catch 'testing::internal::::ClassUniqueToAlwaysTrue'"); + } + + gst_init (&argc, &argv); + + try { + ret = RUN_ALL_TESTS (); + } catch (...) { + g_warning ("catch `testing::internal::GoogleTestFailureException`"); + } + + return ret; +} diff --git a/tests/nnstreamer_plugins/unittest_plugins.cc b/tests/nnstreamer_plugins/unittest_plugins.cc index 25a7e3b..4b3adc3 100644 --- a/tests/nnstreamer_plugins/unittest_plugins.cc +++ b/tests/nnstreamer_plugins/unittest_plugins.cc @@ -7097,6 +7097,7 @@ TEST (testConverterSubplugins, flexbufInvalidParam1_n) } #endif /** ENABLE_FLATBUF && ENABLE_PROTOBUF */ + /** * @brief Data structure for tensor-crop test. */ -- 2.7.4