[ NNSTREAMER ] Add nnstreamer filter for nntrainer submit/tizen/20200911.060431
authorjijoong.moon <jijoong.moon@samsung.com>
Fri, 11 Sep 2020 04:04:33 +0000 (13:04 +0900)
committerJijoong Moon <jijoong.moon@samsung.com>
Fri, 11 Sep 2020 06:03:27 +0000 (15:03 +0900)
In this PR,

 . NNStreamer filter subplugin is implemented.
 . Test Cases for subplugin

**Self evaluation:**
1. Build test:  [X]Passed [ ]Failed [ ]Skipped
2. Run test:  [X]Passed [ ]Failed [ ]Skipped

Signed-off-by: jijoong.moon <jijoong.moon@samsung.com>
15 files changed:
Applications/Training/jni/meson.build
debian/control
meson.build
meson_options.txt
nnstreamer/tensor_filter/meson.build [new file with mode: 0644]
nnstreamer/tensor_filter/tensor_filter_nntrainer.cc [new file with mode: 0644]
packaging/nntrainer.spec
test/nnstreamer_filter_nntrainer/checkLabel.py [new file with mode: 0644]
test/nnstreamer_filter_nntrainer/gen24bBMP.py [new file with mode: 0644]
test/nnstreamer_filter_nntrainer/runTest.sh [new file with mode: 0755]
test/test_models/data/0.png [new file with mode: 0644]
test/test_models/data/2.png [new file with mode: 0644]
test/test_models/data/9.png [new file with mode: 0644]
test/test_models/models/mnist.ini [new file with mode: 0644]
test/test_models/models/model.bin [new file with mode: 0644]

index 8b080ac..106bc82 100644 (file)
@@ -1,5 +1,8 @@
+gst_api_version = '1.0'
 build_root = meson.build_root()
 res_path = join_paths(meson.current_source_dir(), '..', 'res')
+glib_dep = dependency('glib-2.0')
+gst_dep = dependency('gstreamer-'+gst_api_version)
 
 training_sources = [
   'main.cpp',
index bd9bdee..e91080c 100644 (file)
@@ -8,7 +8,9 @@ Build-Depends: gcc-9 | gcc-8 | gcc-7 | gcc-6 | gcc-5 (>=5.4),
  libopenblas-dev, libiniparser-dev, tensorflow-lite-dev, libjsoncpp-dev,
  libcurl3-gnutls-dev | libcurl4-gnutls-dev | libcurl3-openssl-dev |
  libcurl4-openssl-dev | libcurl3-nns-dev | libcurl4-nns-dev, libgtest-dev,
- libflatbuffers-dev
+ libflatbuffers-dev, libglib2.0-dev, nnstreamer-dev, libgstreamer1.0-dev,
+ libgstreamer-plugins-base1.0-dev, gstreamer1.0-tools, gstreamer1.0-plugins-base,
+ gstreamer1.0-plugins-good
 Standards-Version: 3.9.6
 Homepage: https://github.com/nnstreamer/nntrainer
 
index 9024e42..1f41776 100644 (file)
@@ -144,3 +144,8 @@ endif
 if get_option('enable-test')
    subdir('test')
 endif
+
+if get_option('enable-nnstreamer-tensor-filter')
+   nnstreamer_dep = dependency('nnstreamer', required: true)
+   subdir('nnstreamer/tensor_filter')
+endif
index 5a4b3f0..3c0e4ef 100644 (file)
@@ -8,3 +8,4 @@ option('enable-capi', type: 'boolean', value: true)
 option('enable-test', type: 'boolean', value: true)
 option('enable-logging', type: 'boolean', value: true)
 option('enable-tizen-feature-check', type: 'boolean', value: true)
+option('enable-nnstreamer-tensor-filter', type: 'boolean', value: true)
diff --git a/nnstreamer/tensor_filter/meson.build b/nnstreamer/tensor_filter/meson.build
new file mode 100644 (file)
index 0000000..8f51a1e
--- /dev/null
@@ -0,0 +1,30 @@
+filter_sub_nntrainer_sources = ['tensor_filter_nntrainer.cc']
+
+nnstreamer_filter_nntrainer_sources = []
+foreach s : filter_sub_nntrainer_sources
+  nnstreamer_filter_nntrainer_sources += join_paths(meson.current_source_dir(), s)
+endforeach
+
+nntrainer_prefix = get_option('prefix')
+
+nnstreamer_filter_nntrainer_deps = [glib_dep, gst_dep, nntrainer_dep, nnstreamer_dep]
+
+nnstreamer_libdir = join_paths(nntrainer_prefix, get_option('libdir'))
+subplugin_install_prefix = join_paths(nnstreamer_libdir, 'nnstreamer')
+filter_subplugin_install_dir = join_paths(subplugin_install_prefix, 'filters')
+
+shared_library('nnstreamer_filter_nntrainer',
+  nnstreamer_filter_nntrainer_sources,
+  dependencies: nnstreamer_filter_nntrainer_deps,
+  include_directories: nntrainer_inc,
+  install: true,
+  install_dir: filter_subplugin_install_dir
+)
+
+static_library('nnstreamer_filter_nntrainer',
+  nnstreamer_filter_nntrainer_sources,
+  dependencies: nnstreamer_filter_nntrainer_deps,
+  include_directories: nntrainer_inc,
+  install: true,
+  install_dir: nnstreamer_libdir
+)
diff --git a/nnstreamer/tensor_filter/tensor_filter_nntrainer.cc b/nnstreamer/tensor_filter/tensor_filter_nntrainer.cc
new file mode 100644 (file)
index 0000000..fccfef2
--- /dev/null
@@ -0,0 +1,367 @@
+/* SPDX-License-Identifier: LGPL-2.1-only */
+/**
+ * GStreamer Tensor_Filter, nntrainer Module
+ * Copyright (C) 2020 Jijoong Moon <jijoong.moon@samsung.com>
+ */
+/**
+ * @file       tensor_filter_nntrainer.cc
+ * @date       09 Sept 2020
+ * @brief      nntrainer inference module for tensor_filter gstreamer plugin
+ * @see                http://github.com/nnsuite/nnstreamer
+ * @author     Jijoong Moon <jijoong.moon@samsung.com>
+ * @bug                No known bugs except for NYI items
+ *
+ * This is the per-NN-framework plugin (tensorflow-lite) for tensor_filter.
+ * Fill in "GstTensorFilterFramework" for tensor_filter.h/c
+ *
+ */
+
+#include <algorithm>
+#include <limits.h>
+#include <unistd.h>
+
+#include <nnstreamer_plugin_api.h>
+#include <nnstreamer_plugin_api_filter.h>
+
+#include <neuralnet.h>
+
+#define ml_loge g_critical
+
+/**
+ * @brief Macro for debug mode.
+ */
+#ifndef DBG
+#define DBG FALSE
+#endif
+
+#define NUM_DIM 4
+
+static const gchar *nntrainer_accl_support[] = {NULL};
+
+/**
+ * @brief      Internal data structure for nntrainer
+ */
+typedef struct {
+  int rank;
+  std::vector<std::int64_t> dims;
+} nntrainer_tensor_info_s;
+
+class NNTrainer {
+public:
+  /**
+   * member functions.
+   */
+  NNTrainer(const char *model_config_);
+  ~NNTrainer();
+
+  int init(const GstTensorFilterProperties *prop);
+  int loadModel();
+  const char *getModelConfig();
+
+  int getInputTensorDim(GstTensorsInfo *info);
+  int getOutputTensorDim(GstTensorsInfo *info);
+  int run(const GstTensorMemory *input, GstTensorMemory *output);
+  void freeOutputTensor(void *data);
+  int validateTensor(const GstTensorsInfo *tensorInfo, bool is_input);
+
+private:
+  char *model_config;
+  nntrainer::NeuralNetwork *model;
+
+  GstTensorsInfo inputTensorMeta;
+  GstTensorsInfo outputTensorMeta;
+
+  std::vector<nntrainer_tensor_info_s> input_tensor_info;
+  std::map<void *, std::shared_ptr<nntrainer::Tensor>> outputTensorMap;
+};
+
+void init_filter_nntrainer(void) __attribute__((constructor));
+void fini_filter_nntrainer(void) __attribute__((destructor));
+
+NNTrainer::NNTrainer(const char *model_config_) {
+  g_assert(model_config_ != NULL);
+  model = nullptr;
+  model_config = g_strdup(model_config_);
+  gst_tensors_info_init(&inputTensorMeta);
+  gst_tensors_info_init(&outputTensorMeta);
+}
+
+NNTrainer::~NNTrainer() {
+  if (model != nullptr) {
+    model->finalize();
+    delete model;
+  }
+
+  gst_tensors_info_free(&inputTensorMeta);
+  gst_tensors_info_free(&outputTensorMeta);
+  g_free(model_config);
+}
+
+int NNTrainer::init(const GstTensorFilterProperties *prop) {
+  if (loadModel()) {
+    ml_loge("Failed to load model");
+    return -1;
+  }
+
+  if (validateTensor(&prop->input_meta, true)) {
+    ml_loge("Failed to validate input tensor");
+    return -2;
+  }
+
+  if (validateTensor(&prop->output_meta, false)) {
+    ml_loge("Failed to validate output tensor");
+    return -3;
+  }
+
+  try {
+    model->init();
+    model->readModel();
+  } catch (...) {
+    ml_loge("Failed to initialize model");
+    model->finalize();
+    return -1;
+  }
+
+  gst_tensors_info_copy(&inputTensorMeta, &prop->input_meta);
+  gst_tensors_info_copy(&outputTensorMeta, &prop->output_meta);
+
+  return 0;
+}
+
+const char *NNTrainer::getModelConfig() { return model_config; }
+
+int NNTrainer::validateTensor(const GstTensorsInfo *tensorInfo, bool is_input) {
+
+  nntrainer::TensorDim dim;
+  nntrainer_tensor_info_s info_s;
+  unsigned int order[3] = {1, 3, 2};
+
+  if (is_input)
+    dim = model->getInputDimension();
+  else
+    dim = model->getOutputDimension();
+
+  g_assert(tensorInfo->info[0].type == _NNS_FLOAT32);
+  info_s.rank = NUM_DIM;
+
+  for (unsigned int i = 0; i < NUM_DIM - 1; ++i) {
+    g_assert(tensorInfo->info[0].dimension[i] == dim.getDim()[order[i]]);
+    info_s.dims.push_back(dim.getDim()[order[i]]);
+  }
+
+  g_assert(tensorInfo->info[0].dimension[NUM_DIM - 1] == dim.getDim()[0]);
+  info_s.dims.push_back(dim.getDim()[0]);
+
+  if (is_input) {
+    input_tensor_info.push_back(info_s);
+  }
+
+  return 0;
+}
+
+int NNTrainer::loadModel() {
+#if (DBG)
+  gint64 start_time = g_get_real_time();
+#endif
+  gsize file_size;
+  gchar *content = nullptr;
+  GError *file_error = nullptr;
+
+  g_assert(model_config != nullptr);
+  if (!g_file_test(model_config, G_FILE_TEST_IS_REGULAR)) {
+    ml_loge("the file of model_config (%s) is not valid (not regular)\n",
+            model_config);
+    return -1;
+  }
+
+  if (!g_file_get_contents(model_config, &content, &file_size, &file_error)) {
+    ml_loge("Error reading model config!! - %s", file_error->message);
+    g_clear_error(&file_error);
+    return -2;
+  }
+
+  try {
+    model = new nntrainer::NeuralNetwork(false);
+    model->loadFromConfig(model_config);
+  } catch (...) {
+    ml_loge("Cannot load model from config\n");
+    model->finalize();
+    return -1;
+  }
+
+#if (DBG)
+  gint64 stop_time = g_get_real_time();
+  g_message("Model is loaded: %" G_GINT64_FORMAT, (stop_time - start_time));
+#endif
+  return 0;
+}
+
+int NNTrainer::getInputTensorDim(GstTensorsInfo *info) {
+  gst_tensors_info_copy(info, &inputTensorMeta);
+  return 0;
+}
+
+int NNTrainer::getOutputTensorDim(GstTensorsInfo *info) {
+  gst_tensors_info_copy(info, &outputTensorMeta);
+  return 0;
+}
+
+int NNTrainer::run(const GstTensorMemory *input, GstTensorMemory *output) {
+#if (DBG)
+  gint64 start_time = g_get_real_time();
+#endif
+  std::vector<std::shared_ptr<nntrainer::Tensor>> output_tensors;
+  std::shared_ptr<nntrainer::Tensor> out;
+
+  std::vector<std::int64_t> d = input_tensor_info[0].dims;
+  nntrainer::Tensor X =
+    nntrainer::Tensor(nntrainer::TensorDim(d[3], d[0], d[2], d[1]),
+                      static_cast<float *>(input[0].data));
+
+  output_tensors.push_back(out);
+  std::shared_ptr<const nntrainer::Tensor> o;
+
+  o = model->inference(X);
+  if (o == nullptr) {
+    model->finalize();
+    return -1;
+  }
+
+  out = std::const_pointer_cast<nntrainer::Tensor>(o);
+
+  output[0].data = out->getData();
+
+  outputTensorMap.insert(std::make_pair(output[0].data, output_tensors[0]));
+
+#if (DBG)
+  gint64 stop_time = g_get_real_time();
+  g_message("Run() is finished: %" G_GINT64_FORMAT, (stop_time - start_time));
+#endif
+  return 0;
+}
+
+void NNTrainer::freeOutputTensor(void *data) {
+  if (data != nullptr) {
+    std::map<void *, std::shared_ptr<nntrainer::Tensor>>::iterator it =
+      outputTensorMap.find(data);
+    if (it != outputTensorMap.end()) {
+      outputTensorMap.erase(data);
+    }
+  }
+}
+
+static void nntrainer_close(const GstTensorFilterProperties *prop,
+                            void **private_data) {
+  NNTrainer *nntrainer = static_cast<NNTrainer *>(*private_data);
+
+  if (!nntrainer)
+    return;
+  delete nntrainer;
+  *private_data = NULL;
+}
+
+static int nntrainer_loadModelFile(const GstTensorFilterProperties *prop,
+                                   void **private_data) {
+  NNTrainer *nntrainer;
+  const gchar *model_file;
+  if (prop->num_models != 1)
+    return -1;
+
+  nntrainer = static_cast<NNTrainer *>(*private_data);
+  model_file = prop->model_files[0];
+
+  if (nntrainer != NULL) {
+    if (g_strcmp0(model_file, nntrainer->getModelConfig()) == 0)
+      return 1; /* skipped */
+
+    nntrainer_close(prop, private_data);
+  }
+
+  nntrainer = new NNTrainer(model_file);
+  if (nntrainer == NULL) {
+    g_printerr("Failed to allocate memory for filter subplugin: nntrainer\n");
+    return -1;
+  }
+
+  if (nntrainer->init(prop) != 0) {
+    *private_data = NULL;
+    delete nntrainer;
+
+    g_printerr("failed to initailize the object: nntrainer\n");
+    return -2;
+  }
+
+  *private_data = nntrainer;
+  return 0;
+}
+
+static int nntrainer_open(const GstTensorFilterProperties *prop,
+                          void **private_data) {
+  int status = nntrainer_loadModelFile(prop, private_data);
+  return status;
+}
+
+static int nntrainer_run(const GstTensorFilterProperties *prop,
+                         void **private_data, const GstTensorMemory *input,
+                         GstTensorMemory *output) {
+  NNTrainer *nntrainer = static_cast<NNTrainer *>(*private_data);
+  g_return_val_if_fail(nntrainer && input && output, -EINVAL);
+
+  return nntrainer->run(input, output);
+}
+
+static int nntrainer_getInputDim(const GstTensorFilterProperties *prop,
+                                 void **private_data, GstTensorsInfo *info) {
+  NNTrainer *nntrainer = static_cast<NNTrainer *>(*private_data);
+  g_return_val_if_fail(nntrainer && info, -EINVAL);
+  return nntrainer->getInputTensorDim(info);
+}
+
+static int nntrainer_getOutputDim(const GstTensorFilterProperties *prop,
+                                  void **private_data, GstTensorsInfo *info) {
+  NNTrainer *nntrainer = static_cast<NNTrainer *>(*private_data);
+  g_return_val_if_fail(nntrainer && info, -EINVAL);
+  return nntrainer->getOutputTensorDim(info);
+}
+
+static void nntrainer_destroyNotify(void **private_data, void *data) {
+  NNTrainer *nntrainer = static_cast<NNTrainer *>(*private_data);
+  std::cout << "nnstrainer_dest" << std::endl;
+  if (nntrainer) {
+    nntrainer->freeOutputTensor(data);
+  }
+}
+
+static int nntrainer_checkAvailability(accl_hw hw) {
+  if (g_strv_contains(nntrainer_accl_support, get_accl_hw_str(hw)))
+    return 0;
+
+  return -ENOENT;
+}
+
+static gchar filter_subplugin_nntrainer[] = "nntrainer";
+
+static GstTensorFilterFramework NNS_support_nntrainer = {
+  .version = GST_TENSOR_FILTER_FRAMEWORK_V0,
+  .open = nntrainer_open,
+  .close = nntrainer_close,
+};
+
+void init_filter_nntrainer(void) {
+  NNS_support_nntrainer.name = filter_subplugin_nntrainer;
+  NNS_support_nntrainer.allow_in_place = FALSE;
+  NNS_support_nntrainer.allocate_in_invoke = TRUE;
+  NNS_support_nntrainer.run_without_model = FALSE;
+  NNS_support_nntrainer.verify_model_path = FALSE;
+  NNS_support_nntrainer.invoke_NN = nntrainer_run;
+  NNS_support_nntrainer.getInputDimension = nntrainer_getInputDim;
+  NNS_support_nntrainer.getOutputDimension = nntrainer_getOutputDim;
+  NNS_support_nntrainer.destroyNotify = nntrainer_destroyNotify;
+  NNS_support_nntrainer.checkAvailability = nntrainer_checkAvailability;
+
+  nnstreamer_filter_probe(&NNS_support_nntrainer);
+}
+
+void fini_filter_nntrainer(void) {
+  nnstreamer_filter_exit(NNS_support_nntrainer.name);
+}
index 701b72d..1f9ce04 100644 (file)
@@ -1,5 +1,6 @@
 # Execute gbs with --define "testcoverage 1" in case that you must get unittest coverage statistics
 %define         use_cblas 1
+%define         nnstreamer_filter 1
 %define         use_gym 0
 %define                nntrainerapplicationdir %{_libdir}/nntrainer/bin
 %define         test_script $(pwd)/packaging/run_unittests.sh
@@ -31,6 +32,10 @@ BuildRequires:       python3
 BuildRequires: python3-numpy
 BuildRequires: capi-ml-common-devel
 
+%if 0%{?unit_test}
+BuildRequires: ssat >= 1.1.0
+%endif
+
 # OpenAI interface
 %define enable_gym -Duse_gym=false
 %if 0%{?use_gym}
@@ -48,6 +53,10 @@ BuildRequires: lcov
 BuildRequires: pkgconfig(capi-system-info)
 BuildRequires: pkgconfig(capi-base-common)
 BuildRequires: pkgconfig(dlog)
+%if  0%{?nnstreamer_filter}
+BuildRequires: nnstreamer-devel
+%define enable_nnstreamer_tensor_filter -Denable-nnstreamer-tensor-filter=true
+%endif #nnstreamer_filter
 %endif  # tizen
 
 Requires:      iniparser
@@ -161,7 +170,7 @@ CFLAGS="${CFLAGS} -fprofile-arcs -ftest-coverage"
 mkdir -p build
 meson --buildtype=plain --prefix=%{_prefix} --sysconfdir=%{_sysconfdir} \
       --libdir=%{_libdir} --bindir=%{nntrainerapplicationdir} --includedir=%{_includedir}\
-      %{install_app} %{enable_tizen} %{enable_tizen_feature_check} %{enable_cblas} %{enable_gym} build
+      %{install_app} %{enable_tizen} %{enable_tizen_feature_check} %{enable_cblas} %{enable_gym} %{enable_nnstreamer_tensor_filter} build
 
 ninja -C build %{?_smp_mflags}
 
@@ -235,6 +244,7 @@ cp -r result %{buildroot}%{_datadir}/nntrainer/unittest/
 %defattr(-,root,root,-)
 %license LICENSE
 %{_libdir}/libnntrainer.so
+%{_libdir}/nnstreamer/filters/libnnstreamer_filter_nntrainer.so
 
 %files devel
 %{_includedir}/nntrainer/databuffer.h
@@ -281,6 +291,7 @@ cp -r result %{buildroot}%{_datadir}/nntrainer/unittest/
 
 %files -n capi-nntrainer-devel-static
 %{_libdir}/libcapi-nntrainer.a
+%{_libdir}/libnnstreamer_filter_nntrainer.a
 
 %endif #tizen
 
diff --git a/test/nnstreamer_filter_nntrainer/checkLabel.py b/test/nnstreamer_filter_nntrainer/checkLabel.py
new file mode 100644 (file)
index 0000000..0af1590
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+
+##
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+# Copyright (C) 2018 Samsung Electronics
+#
+# @file checkLabel.py
+# @brief Check the result label of tensorflow-lite model
+# @author HyoungJoo Ahn <hello.ahn@samsung.com>
+
+import sys
+import os
+import struct
+import string
+sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
+from gen24bBMP  import convert_to_bytes
+
+if sys.version_info < (3,):
+    range = xrange
+
+def readbyte (filename):
+    F = open(filename, 'rb')
+    readbyte = F.read()
+    F.close()
+    return readbyte
+
+
+def readlabel (filename):
+    F = open(filename, 'r')
+    line = F.readlines()
+    F.close()
+    return line
+
+bytearr = readbyte(sys.argv[1])
+softmax = []
+for i in range(10):
+    byte = b''
+    byte += convert_to_bytes(bytearr[i * 4])
+    byte += convert_to_bytes(bytearr[i * 4 + 1])
+    byte += convert_to_bytes(bytearr[i * 4 + 2])
+    byte += convert_to_bytes(bytearr[i * 4 + 3])
+    softmax.append(struct.unpack('f', byte))
+
+pred = softmax.index(max(softmax))
+answer = int(sys.argv[2].strip())
+exit(pred != answer)
diff --git a/test/nnstreamer_filter_nntrainer/gen24bBMP.py b/test/nnstreamer_filter_nntrainer/gen24bBMP.py
new file mode 100644 (file)
index 0000000..eea2278
--- /dev/null
@@ -0,0 +1,485 @@
+#!/usr/bin/env python
+
+##
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+# Copyright (C) 2018 Samsung Electronics
+#
+# @file gen24bBMP.py
+# @brief Generate 24bpp .bmp files for test cases
+# @author MyungJoo Ham <myungjoo.ham@samsung.com>
+
+from __future__ import print_function
+
+from struct import pack
+import random
+import sys
+
+##
+# @brief Convert given data to bytes
+# @param[in] data The data to be converted to bytes array
+# @return bytes converted from the data
+
+def convert_to_bytes(data):
+    """
+    Convert given data to bytes
+
+    @param  data: The data to be converted to bytes
+    @rtype      : bytes
+    @return     : bytes converted from the data
+    """
+
+    if isinstance(data, bytes):
+        return data
+    else:
+        return pack("<B", data)
+
+##
+# @brief Save bitmap "data" to "filename"
+# @param[in] filename The filename to be saves as a .bmp file.
+# @param[in] data string of RGB (packed in 'BBB') or BGRx (packed in 'BBBB')
+# @param[in] colorspace "RGB" or "BGRx"
+# @param[in] width Width of the picture
+# @param[in] height Height of the picture
+def saveBMP(filename, data, colorspace, width, height):
+    size = len(data)
+    graphics = b''
+    # Default value of bytes per pixel value for RGB
+    bytes_per_px = 3
+
+    if colorspace == 'RGB':
+        assert(size == (width * height * bytes_per_px))
+        # BMP is stored bottom to top. Reverse the order
+        for h in range(height-1, -1, -1):
+            for w in range(0, width):
+                pos = 3 * (w + width * h)
+                graphics += convert_to_bytes(data[pos + 2])
+                graphics += convert_to_bytes(data[pos + 1])
+                graphics += convert_to_bytes(data[pos])
+            for x in range(0, (width * 3) % 4):
+                graphics += pack('<B', 0)
+    elif colorspace == 'BGRx':
+        bytes_per_px = 4
+        assert(size == (width * height * bytes_per_px))
+        # BMP is stored bottom to top. Reverse the order
+        for h in range(height-1, -1, -1):
+            for w in range(0, width):
+                pos = bytes_per_px * (w + width * h)
+                graphics += convert_to_bytes(data[pos])
+                graphics += convert_to_bytes(data[pos + 1])
+                graphics += convert_to_bytes(data[pos + 2])
+            for x in range(0, (width * 3) % 4):
+                graphics += pack('<B', 0)
+    elif colorspace == 'GRAY8':
+        bytes_per_px = 1
+        assert(size == (width * height * bytes_per_px))
+        # BMP is stored bottom to top. Reverse the order
+        for h in range(height-1, -1, -1):
+            for w in range(0, width):
+                pos = bytes_per_px * (w + width * h)
+                graphics += convert_to_bytes(data[pos])
+            for x in range(0, (width * 3) % 4):
+                graphics += pack('<B', 0)
+    else:
+        print('Unrecognized colorspace %', colorspace)
+        sys.exit(1)
+
+    # BMP file header
+    if colorspace == 'GRAY8':
+        header = pack('<HLHHL', 19778, (26 + width * height), 0, 0, 26)
+        header += pack('<LHHHH', 12, width, height, 1, 8)
+    else:
+        header = pack('<HLHHL', 19778, (26 + width * height * 3), 0, 0, 26)
+        header += pack('<LHHHH', 12, width, height, 1, 24)
+
+    with open(filename, 'wb') as file:
+        file.write(header)
+        file.write(graphics)
+
+
+##
+# @brief Write the generated data
+#
+def write(filename, data):
+    with open(filename, 'wb') as file:
+        file.write(data)
+
+
+##
+# @brief Generate Golden Test Case, a single videosrctest frame of 280x40xRGB
+# @return (string, string_size, expected_size)
+#
+# If string_size < expected_size, you do not need to check the results offset >= string_size.
+# string: binary string (b'\xff\x00....')
+def gen_RGB():
+    string = b''
+    string_size = 0
+    expected_size = 280 * 40 * 3
+    for i in range(0, 26):
+        # White
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 255, 255, 255)
+        # Yellow
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 255, 255, 0)
+        # Light Blue
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 0, 255, 255)
+        # Green
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 0, 255, 0)
+        # Purple
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 255, 0, 255)
+        # Red
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 255, 0, 0)
+        # Blue
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 0, 0, 255)
+    for i in range(26, 30):
+        # Blue
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 0, 0, 255)
+        # Black
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 0, 0, 0)
+        # Purple
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 255, 0, 255)
+        # Black
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 0, 0, 0)
+        # Light Blue
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 0, 255, 255)
+        # Black
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 0, 0, 0)
+        # White
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBB', 255, 255, 255)
+    for i in range(0, 46):
+        # Dark Blue
+        string_size = string_size + 46
+        string += pack('BBB', 0, 0, 128)
+    for i in range(46, 93):
+        # White
+        string_size = string_size + 47
+        string += pack('BBB', 255, 255, 255)
+    for i in range(93, 140):
+        # Gray Blue
+        string_size = string_size + 47
+        string += pack('BBB', 0, 128, 255)
+    for i in range(140, 186):
+        # Black
+        string_size = string_size + 46
+        string += pack('BBB', 0, 0, 0)
+    for i in range(186, 210):
+        # Dark Gray
+        string_size = string_size + 24
+        string += pack('BBB', 19, 19, 19)
+    # We do not check the reset pixels: they are randomly generated.
+    string_size = string_size * 3
+    return string, string_size, expected_size
+
+
+##
+# @brief Generate Golden Test Case, a single videosrctest frame of 280x40xBGRx
+# @return (string, string_size, expected_size)
+#
+def gen_BGRx():
+    string = b''
+    string_size = 0
+    expected_size = 280 * 40 * 4
+    for i in range(0, 26):
+        # White
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 255, 255, 255, 255)
+        # Yellow
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 0, 255, 255, 255)
+        # Light Blue
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 255, 255, 0, 255)
+        # Green
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 0, 255, 0, 255)
+        # Purple
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 255, 0, 255, 255)
+        # Red
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 0, 0, 255, 255)
+        # Blue
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 255, 0, 0, 255)
+    for i in range(26, 30):
+        # Blue
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 255, 0, 0, 255)
+        # Black
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 0, 0, 0, 255)
+        # Purple
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 255, 0, 255, 255)
+        # Black
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 0, 0, 0, 255)
+        # Light Blue
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 255, 255, 0, 255)
+        # Black
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 0, 0, 0, 255)
+        # White
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('BBBB', 255, 255, 255, 255)
+    for i in range(0, 46):
+        # Dark Blue
+        string_size = string_size + 46
+        string += pack('BBBB', 128, 0, 0, 255)
+    for i in range(46, 93):
+        # White
+        string_size = string_size + 47
+        string += pack('BBBB', 255, 255, 255, 255)
+    for i in range(93, 140):
+        # Gray Blue
+        string_size = string_size + 47
+        string += pack('BBBB', 255, 128, 0, 255)
+    for i in range(140, 186):
+        # Black
+        string_size = string_size + 46
+        string += pack('BBBB', 0, 0, 0, 255)
+    for i in range(186, 210):
+        # Dark Gray
+        string_size = string_size + 24
+        string += pack('BBBB', 19, 19, 19, 255)
+    # We do not check the reset pixels: they are randomly generated.
+    string_size = string_size * 4
+    return string, string_size, expected_size
+
+
+##
+# @brief Generate Golden Test Case, a single videosrctest frame of 280x40xGRAY8
+# @return (string, string_size, expected_size)
+#
+# If string_size < expected_size, you do not need to check the results offset >= string_size.
+# string: binary string (b'\xff\x00....')
+def gen_GRAY8():
+    string = b''
+    string_size = 0
+    expected_size = 280 * 40
+    for i in range(0, 26):
+        # 0xEB
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 235)
+        # 0xD2
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 210)
+        # 0xAA
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 170)
+        # 0x91
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 145)
+        # 0x6A
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 106)
+        # 0x51
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 81)
+        # 0x29
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 41)
+    for i in range(26, 30):
+        # 0x29
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 41)
+        # 0x10
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 16)
+        # 0x6A
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 106)
+        # 0x10
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 16)
+        # 0xAA
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 170)
+        # 0x10
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 16)
+        # 0xEB
+        string_size = string_size + 40
+        for j in range(0, 40):
+            string += pack('B', 235)
+    for i in range(0, 46):
+        # 0x10
+        string_size = string_size + 46
+        string += pack('B', 16)
+    for i in range(46, 93):
+        # 0xEB
+        string_size = string_size + 47
+        string += pack('B', 235)
+    for i in range(93, 140):
+        # 0x10
+        string_size = string_size + 47
+        string += pack('B', 16)
+    for i in range(140, 163):
+        # 0x00
+        string_size = string_size + 23
+        string += pack('B', 0)
+    for i in range(163, 186):
+        # 0x10
+        string_size = string_size + 23
+        string += pack('B', 16)
+    for i in range(186, 210):
+        # 0x20
+        string_size = string_size + 24
+        string += pack('B', 32)
+    # We do not check the reset pixels: they are randomly generated.
+    return string, string_size, expected_size
+
+##
+# @brief Generate Golden Test Case, a randomly generated BMP image
+# @return (string, string_size, expected_size)
+#
+def gen_BMP_random(color_type, width, height, filename_prefix):
+    string = b''
+    string_size = 0
+    size_per_pixel = 3
+    if color_type == 'BGRx':
+        size_per_pixel = 4
+    elif color_type == "GRAY8":
+        size_per_pixel = 1
+    expected_size = width * height * size_per_pixel
+    # The result has no stride for other/tensor types.
+
+    if color_type == 'BGRx':
+        for y in range(0, height):
+            for x in range(0, width):
+                pval = (random.randrange(256), random.randrange(256), random.randrange(256))
+                pixel = pack('BBBB', pval[2], pval[1], pval[0], 255)
+                string += pixel
+                string_size += 4
+    elif color_type == 'GRAY8':
+        for y in range(0, height):
+            for x in range(0, width):
+                pval = random.randrange(256)
+                pixel = pack('B', pval)
+                string += pixel
+                string_size += size_per_pixel
+    else:
+        # Assume RGB
+        for y in range(0, height):
+            for x in range(0, width):
+                pval = (random.randrange(256), random.randrange(256), random.randrange(256))
+                pixel = pack('BBB', pval[0], pval[1], pval[2])
+                string += pixel
+                string_size += 3
+
+    saveBMP(filename_prefix + '_' + color_type + '_' + str(width) + 'x' + str(height) + '.bmp',
+            string, color_type, width, height)
+    return string, string_size, expected_size
+
+
+##
+# @brief Generate a fixed BMP sequence for stream test
+# @return 0 if success. non-zero if failed.
+#
+# This gives "16x16", black, white, green, red, blue, wb-checker, rb-checker, gr-checker,
+# red-cross-on-white, blue-cross-on-black (4x4 x 16, left-top/right-bottom white/red/green).
+# "10 files" with 0 ~ 9 postfix in the filename
+def gen_BMP_stream(filename_prefix, golden_filename, num_sink):
+    string = [b'' for _ in range(10)]
+    size_x = 16
+    size_y = 16
+
+    for y in range(0, size_y):
+        for x in range(0, size_x):
+            # black. Frame 0
+            string[0] += pack('BBB', 0, 0, 0)
+            # white. Frame 1
+            string[1] += pack('BBB', 255, 255, 255)
+            # green, Frame 2
+            string[2] += pack('BBB', 0, 255, 0)
+            # red, Frame 3
+            string[3] += pack('BBB', 255, 0, 0)
+            # blue, Frame 4
+            string[4] += pack('BBB', 0, 0, 255)
+            # white-black checker, Frame 5
+            if (((x / 4) % 2) + ((y / 4) % 2)) == 1:
+                string[5] += pack('BBB', 0, 0, 0)
+            else:
+                string[5] += pack('BBB', 255, 255, 255)
+            # red-blue checker, Frame 6
+            if (((x / 4) % 2) + ((y / 4) % 2)) == 1:
+                string[6] += pack('BBB', 0, 0, 255)
+            else:
+                string[6] += pack('BBB', 255, 0, 0)
+            # green-red checker, Frame 7
+            if (((x / 4) % 2) + ((y / 4) % 2)) == 1:
+                string[7] += pack('BBB', 255, 0, 0)
+            else:
+                string[7] += pack('BBB', 0, 255, 0)
+            # red-cross-on-white, Frame 8
+            if x == y:
+                string[8] += pack('BBB', 255, 0, 0)
+            else:
+                string[8] += pack('BBB', 255, 255, 255)
+            # blue-cross-on-black, Frame 9
+            if x == y:
+                string[9] += pack('BBB', 0, 0, 255)
+            else:
+                string[9] += pack('BBB', 0, 0, 0)
+
+    with open(golden_filename, 'wb') as file:
+        for i in range(0, 10):
+            saveBMP(filename_prefix + '_' + str(i) + '.bmp', string[i], 'RGB', 16, 16)
+            for j in range(0, num_sink):
+                file.write(string[i])
+    return string
diff --git a/test/nnstreamer_filter_nntrainer/runTest.sh b/test/nnstreamer_filter_nntrainer/runTest.sh
new file mode 100755 (executable)
index 0000000..0aefd1d
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+##
+## SPDX-License-Identifier: LGPL-2.1-only
+##
+## @file runTest.sh
+## @author Jijoong Moon <jijoong.moon@samsung.com>
+## @date Sept 10 2020
+## @brief SSAT Test Cases for NNTrainer tensor filter
+##
+
+if [[ "$SSATAPILOADED" != "1" ]]; then
+    SILENT=0
+    INDEPENDENT=1
+    search="ssat-api.sh"
+    source $search
+    printf "${Blue}Independent Mode${NC}
+"
+fi
+
+if [ -z ${SO_EXT} ]; then
+    SO_EXT="so"
+fi
+
+# This is compatible with SSAT (https://github.com/myungjoo/SSAT)
+testInit $1
+
+# NNStreamer and plugins path for test
+PATH_TO_PLUGIN="../../build"
+
+if [[ -d $PATH_TO_PLUGIN ]]; then
+    ini_path="${PATH_TO_PLUGIN}/ext/nnstreamer/tensor_filter"
+    if [[ -d ${ini_path} ]]; then
+        check=$(ls ${ini_path} | grep nntrainer.${SO_EXT})
+        if [[ ! $check ]]; then
+            echo "Cannot find nntrainer shared lib"
+            report
+            exit
+        fi
+    else
+        echo "Cannot find ${ini_path}"
+    fi
+else
+    ini_file="/etc/nnstreamer.ini"
+    if [[ -f ${ini_file} ]]; then
+        path=$(grep "^filters" ${ini_file})
+        key=${path%=*}
+        value=${path##*=}
+
+        if [[ $key != "filters" ]]; then
+            echo "String Error"
+            report
+            exit
+        fi
+
+        if [[ -d ${value} ]]; then
+            check=$(ls ${value} | grep nntrainer.${SO_EXT})
+            if [[ ! $check ]]; then
+                echo "Cannot find nntrainer shared lib"
+                report
+                exit
+            fi
+        else
+            echo "Cannot file ${value}"
+            report
+            exit
+        fi
+    else
+        echo "Cannot identify nnstreamer.ini"
+        report
+        exit
+    fi
+fi
+
+# Test with mnist model
+PATH_TO_CONFIG="../test_models/models/mnist.ini"
+PATH_TO_DATA="../test_models/data/2.png"
+
+gstTest "--gst-plugin-path=${PATH_TO_PLUGIN} filesrc location=${PATH_TO_DATA} ! pngdec ! tensor_converter ! tensor_transform mode=typecast option=float32 ! tensor_filter framework=nntrainer model=${PATH_TO_CONFIG} input=1:28:28:1 inputtype=float32 output=1:10:1:1 outputtype=float32 ! filesink location=nntrainer.out.1.log" 1 0 0 $PERFORMANCE
+python checkLabel.py nntrainer.out.1.log 2
+testResult $? 1 "Golden test comparison" 0 1
+
+PATH_TO_CONFIG="../test_models/models/mnist.ini"
+PATH_TO_DATA="../test_models/data/0.png"
+
+gstTest "--gst-plugin-path=${PATH_TO_PLUGIN} filesrc location=${PATH_TO_DATA} ! pngdec ! tensor_converter ! tensor_transform mode=typecast option=float32 ! tensor_filter framework=nntrainer model=${PATH_TO_CONFIG} input=1:28:28:1 inputtype=float32 output=1:10:1:1 outputtype=float32 ! filesink location=nntrainer.out.2.log" 1 0 0 $PERFORMANCE
+python checkLabel.py nntrainer.out.2.log 0
+testResult $? 2 "Golden test comparison" 0 1
+report
diff --git a/test/test_models/data/0.png b/test/test_models/data/0.png
new file mode 100644 (file)
index 0000000..8da066d
Binary files /dev/null and b/test/test_models/data/0.png differ
diff --git a/test/test_models/data/2.png b/test/test_models/data/2.png
new file mode 100644 (file)
index 0000000..174a283
Binary files /dev/null and b/test/test_models/data/2.png differ
diff --git a/test/test_models/data/9.png b/test/test_models/data/9.png
new file mode 100644 (file)
index 0000000..c81926f
Binary files /dev/null and b/test/test_models/data/9.png differ
diff --git a/test/test_models/models/mnist.ini b/test/test_models/models/mnist.ini
new file mode 100644 (file)
index 0000000..aee2dc1
--- /dev/null
@@ -0,0 +1,63 @@
+# Network Section : Network
+[Model]
+Type = NeuralNetwork   # Network Type : Regression, KNN, NeuralNetwork
+Learning_rate = 1e-4   # Learning Rate
+Epochs = 1500          # Epochs
+Optimizer = adam       # adam (Adamtive Moment Estimation)
+Loss = cross           # Loss function : mse (mean squared error)
+                        #                       cross ( for cross entropy )
+Save_Path = "../test_models/models/model.bin"          # model path to save / read
+batch_size = 32                # batch size
+beta1 = 0.9            # beta 1 for adam
+beta2 = 0.999  # beta 2 for adam
+epsilon = 1e-7 # epsilon for adam
+
+# Layer Section : Name
+[inputlayer]
+Type = input
+Input_Shape = 1:28:28
+
+# Layer Section : Name
+[conv2d_c1_layer]
+Type = conv2d
+kernel_size = 5,5
+bias_initializer=zeros
+Activation=sigmoid
+weight_initializer = xavier_uniform
+filters = 6
+stride = 1,1
+padding = 0,0
+
+[pooling2d_p1]
+Type=pooling2d
+pool_size = 2,2
+stride =2,2
+padding = 0,0
+pooling = average
+
+[conv2d_c2_layer]
+Type = conv2d
+kernel_size = 5,5
+bias_initializer=zeros
+Activation=sigmoid
+weight_initializer = xavier_uniform
+filters = 12
+stride = 1,1
+padding = 0,0
+
+[pooling2d_p2]
+Type=pooling2d
+pool_size = 2,2
+stride =2,2
+padding = 0,0
+pooling = average
+
+[flatten]
+Type=flatten
+
+[outputlayer]
+Type = fully_connected
+Unit = 10              # Output Layer Dimension ( = Weight Width )
+weight_initializer = xavier_uniform
+bias_initializer = zeros
+Activation = softmax   # activation : sigmoid, softmax
diff --git a/test/test_models/models/model.bin b/test/test_models/models/model.bin
new file mode 100644 (file)
index 0000000..ea30d6e
Binary files /dev/null and b/test/test_models/models/model.bin differ