[PythonFilter/Subplugin] Add a subplugin for Python custom filters
authorDongju Chae <dongju.chae@samsung.com>
Thu, 11 Apr 2019 09:32:39 +0000 (18:32 +0900)
committerwooksong <wook16.song@samsung.com>
Fri, 12 Apr 2019 07:26:18 +0000 (16:26 +0900)
It's a nnstreamer subplugin for custom filters written in Python.
It supports both Python 2 and 3.

Signed-off-by: Dongju Chae <dongju.chae@samsung.com>
ext/nnstreamer/tensor_filter/tensor_filter_python.c [new file with mode: 0644]
ext/nnstreamer/tensor_filter/tensor_filter_python_core.cc [new file with mode: 0644]
ext/nnstreamer/tensor_filter/tensor_filter_python_core.h [new file with mode: 0644]

diff --git a/ext/nnstreamer/tensor_filter/tensor_filter_python.c b/ext/nnstreamer/tensor_filter/tensor_filter_python.c
new file mode 100644 (file)
index 0000000..f52501a
--- /dev/null
@@ -0,0 +1,241 @@
+/**
+ * GStreamer Tensor_Filter, Python Module
+ * Copyright (C) 2019 Dongju Chae <dongju.chae@samsung.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ */
+/**
+ * @file       tensor_filter_python.c
+ * @date       10 Apr 2019
+ * @brief      python module for tensor_filter gstreamer plugin
+ * @see                http://github.com/nnsuite/nnstreamer
+ * @author     Dongju Chae <dongju.chae@samsung.com>
+ * @bug                No known bugs except for NYI items
+ */
+
+#define NO_IMPORT_ARRAY
+#include "tensor_filter_python_core.h"
+#include <nnstreamer_plugin_api_filter.h>
+#include <glib.h>
+#include <string.h>
+#include <nnstreamer_conf.h>
+
+/**
+ * @brief internal data of python
+ */
+struct _py_data
+{
+  void *py_private_data;
+};
+typedef struct _py_data py_data;
+
+static GstTensorFilterFramework *NNS_support_python;
+
+/**
+ * @brief The mandatory callback for GstTensorFilterFramework
+ * @param prop: property of tensor_filter instance
+ * @param private_data : python plugin's private data
+ * @param[in] input The array of input tensors
+ * @param[out] output The array of output tensors
+ */
+static int
+py_run (const GstTensorFilterProperties * prop, void **private_data,
+    const GstTensorMemory * input, GstTensorMemory * output)
+{
+  py_data *py = *private_data;
+
+  g_assert (py);
+
+  return py_core_run (py->py_private_data, input, output);
+}
+
+/**
+ * @brief The optional callback for GstTensorFilterFramework
+ * @param[in] data The data element.
+ */
+static void
+py_destroyNotify (void *data)
+{
+  py_core_destroyNotify (data);
+}
+
+/**
+ * @brief The optional callback for GstTensorFilterFramework
+ * @param[in] prop read-only property values
+ * @param[in/out] private_data python plugin's private data
+ * @param[in] in_info structure of input tensor info
+ * @param[out] out_info structure of output tensor info
+ */
+static int
+py_setInputDim (const GstTensorFilterProperties * prop, void **private_data,
+    const GstTensorsInfo * in_info, GstTensorsInfo * out_info)
+{
+  py_data *py = *private_data;
+
+  g_assert (py);
+
+  return py_core_setInputDim (py->py_private_data, in_info, out_info);
+}
+
+/**
+ * @brief The optional callback for GstTensorFilterFramework
+ * @param prop: property of tensor_filter instance
+ * @param private_data : python plugin's private data
+ * @param[out] info The dimesions and types of input tensors
+ */
+static int
+py_getInputDim (const GstTensorFilterProperties * prop, void **private_data,
+    GstTensorsInfo * info)
+{
+  py_data *py = *private_data;
+
+  g_assert (py);
+
+  return py_core_getInputDim (py->py_private_data, info);
+}
+
+/**
+ * @brief The optional callback for GstTensorFilterFramework
+ * @param prop: property of tensor_filter instance
+ * @param private_data : python plugin's private data
+ * @param[out] info The dimesions and types of output tensors
+ */
+static int
+py_getOutputDim (const GstTensorFilterProperties * prop, void **private_data,
+    GstTensorsInfo * info)
+{
+  py_data *py = *private_data;
+
+  g_assert (py);
+  
+  return py_core_getOutputDim (py->py_private_data, info);
+}
+
+/**
+ * @brief Free privateData and move on.
+ */
+static void
+py_close (const GstTensorFilterProperties * prop, void **private_data)
+{
+  py_data *py = *private_data;
+
+  g_assert (py);
+  py_core_delete (py->py_private_data);
+  g_free (py);
+
+  *private_data = NULL;
+}
+
+/**
+ * @brief Load python model
+ * @param prop: property of tensor_filter instance
+ * @param private_data : python plugin's private data
+ * @return 0 if successfully loaded. 1 if skipped (already loaded).
+ *        -1 if the object construction is failed.
+ *        -2 if the object initialization if failed
+ */
+static int
+py_loadScriptFile (const GstTensorFilterProperties * prop, void **private_data)
+{
+  /**
+   * prop->model_file contains the path of a python script
+   * prop->custom contains its arguments seperated by ' '
+   */
+  py_data *py;
+
+  if (*private_data != NULL) {
+    py = *private_data;
+    if (strcmp (prop->model_file, py_core_getScriptPath (py->py_private_data))) {
+      py_close (prop, private_data);
+    } else {
+      return 1;
+    }
+  }
+
+  py = g_new0 (py_data, 1); /** initialize py Fill Zero! */
+  *private_data = py;
+  py->py_private_data = py_core_new (prop->model_file, prop->custom_properties);
+
+  if (py->py_private_data) {
+    if (py_core_init (py->py_private_data, prop)) {
+      g_printerr ("failed to initailize the object: python");
+      return -2;
+    }
+    
+    /** check methods in python script */
+    cb_type type = py_core_getCbType (py->py_private_data);
+    switch (type) {
+      case CB_SETDIM:
+        NNS_support_python->getInputDimension = NULL;
+        NNS_support_python->getOutputDimension = NULL;
+        NNS_support_python->setInputDimension = &py_setInputDim;
+        break;
+      case CB_GETDIM:
+        NNS_support_python->getInputDimension = &py_getInputDim;
+        NNS_support_python->getOutputDimension = &py_getOutputDim;
+        NNS_support_python->setInputDimension = NULL;
+        break;
+      default:
+        g_printerr ("Wrong callback type");
+        return -2;
+    }
+
+    return 0;
+  } else {
+    g_printerr ("failed to create the object: python");
+    return -1;
+  }
+}
+
+/**
+ * @brief The open callback for GstTensorFilterFramework. Called before anything else
+ * @param prop: property of tensor_filter instance
+ * @param private_data : python plugin's private data
+ */
+static int
+py_open (const GstTensorFilterProperties * prop, void **private_data)
+{
+  return py_loadScriptFile (prop, private_data);
+}
+
+GstTensorFilterFramework _NNS_support_python = {
+#if PY_VERSION_HEX >= 0x03000000
+  .name = "python3",
+#else
+  .name = "python2",
+#endif
+  .allow_in_place = FALSE,      /** @todo: support this to optimize performance later. */
+  .allocate_in_invoke = TRUE,
+  .invoke_NN = py_run,
+  .destroyNotify = py_destroyNotify,
+  /** dimension-related callbacks are dynamically assigned */
+  .getInputDimension = py_getInputDim,
+  .getOutputDimension = py_getOutputDim,
+  .setInputDimension = py_setInputDim,
+  .open = py_open,
+  .close = py_close,
+};
+
+/** @brief Initialize this object for tensor_filter subplugin runtime register */
+__attribute__ ((constructor))
+     void init_filter_py (void)
+{
+  NNS_support_python = &_NNS_support_python;
+  tensor_filter_probe (NNS_support_python);
+}
+
+/** @brief Destruct the subplugin */
+__attribute__ ((destructor))
+     void fini_filter_py (void)
+{
+  tensor_filter_exit (NNS_support_python->name);
+}
diff --git a/ext/nnstreamer/tensor_filter/tensor_filter_python_core.cc b/ext/nnstreamer/tensor_filter/tensor_filter_python_core.cc
new file mode 100644 (file)
index 0000000..c90f1bf
--- /dev/null
@@ -0,0 +1,722 @@
+/**
+ * Copyright (C) 2019 Samsung Electronics Co., Ltd. All rights reserved.
+ * Copyright (C) 2019 Dongju Chae <dongju.chae@samsung.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ */
+/**
+ * @file   tensor_filter_python_core.cc
+ * @author Dongju Chae <dongju.chae@samsung.com>
+ * @date   03/29/2019
+ * @brief  connection with python libraries.
+ * @bug     No known bugs.
+ */
+
+#include "tensor_filter_python_core.h"
+#include <nnstreamer_plugin_api.h>
+
+/**
+ * @brief Macro for debug mode.
+ */
+#ifndef DBG
+#define DBG FALSE
+#endif
+
+std::map <void*, PyArrayObject*> PYCore::outputArrayMap;
+
+/**
+ * @brief      PYCore creator
+ * @param      _script_path    : the logical path to '{script_name}.py' file
+ * @note       the script of _script_path will be loaded simultaneously
+ * @return     Nothing
+ */
+PYCore::PYCore (const char* _script_path, const char* _custom)
+  : script_path(_script_path), module_args(_custom)
+{
+  /**
+   * To fix import error of python extension modules
+   * (e.g., multiarray.x86_64-linux-gnu.so: undefined symbol: PyExc_SystemError)
+   */
+#if PY_VERSION_HEX >= 0x03000000
+  handle = dlopen("libpython3.5m.so.1", RTLD_LAZY | RTLD_GLOBAL);
+  if (handle == NULL) /** try another version */ 
+    handle = dlopen("libpython3.6m.so.1", RTLD_LAZY | RTLD_GLOBAL);
+#else
+  handle = dlopen("libpython2.7.so.1", RTLD_LAZY | RTLD_GLOBAL);
+#endif
+  g_assert(handle);
+
+  Py_Initialize();
+  g_assert(Py_IsInitialized());
+
+  _import_array();  /** for numpy */
+
+  /**
+   * Parse script path to get module name
+   * The module name should drop its extension (i.e., .py)
+   */
+  module_name = script_path;
+
+  const size_t last_idx = module_name.find_last_of("/\\");
+  if (last_idx != std::string::npos)
+    module_name.erase(0, last_idx + 1);
+
+  const size_t ext_idx = module_name.rfind('.');
+  if (ext_idx != std::string::npos)
+    module_name.erase(ext_idx);
+
+  /** Add current/directory path to sys.path */
+  PyObject *sys_module = PyImport_ImportModule("sys");
+  PyObject *sys_path = PyObject_GetAttrString(sys_module, "path");
+
+  PyList_Append(sys_path, PyUnicode_FromString("."));
+  PyList_Append(sys_path, PyUnicode_FromString(script_path.substr(0, last_idx).c_str()));
+
+  Py_XDECREF(sys_path);
+  Py_XDECREF(sys_module);
+
+  /** Find nnstreamer_api module */
+#if PY_VERSION_HEX >= 0x03000000
+  PyObject *api_module = PyImport_ImportModule("nnstreamer_python3");
+#else
+  PyObject *api_module = PyImport_ImportModule("nnstreamer_python2");
+#endif
+  g_assert(api_module);
+  shape_cls = PyObject_GetAttrString(api_module, "TensorShape"); 
+  Py_XDECREF(api_module);
+
+  gst_tensors_info_init (&inputTensorMeta);
+  gst_tensors_info_init (&outputTensorMeta);
+
+  configured = false;
+
+  /** to prevent concurrent Python C-API calls */
+  pthread_mutex_init(&py_mutex, NULL);
+}
+
+/**
+ * @brief      PYCore Destructor
+ * @return     Nothing
+ */
+PYCore::~PYCore ()
+{
+  gst_tensors_info_free (&inputTensorMeta);
+  gst_tensors_info_free (&outputTensorMeta);
+
+  if (core_obj)
+    Py_XDECREF(core_obj);
+  if (shape_cls)
+    Py_XDECREF(shape_cls);
+
+  PyErr_Clear();
+  Py_Finalize();
+
+  dlclose(handle);
+}
+
+/**
+ * @brief      initialize the object with python script
+ * @return 0 if OK. non-zero if error.
+ */
+int
+PYCore::init (const GstTensorFilterProperties * prop)
+{
+  gst_tensors_info_copy (&inputTensorMeta, &prop->input_meta);
+  gst_tensors_info_copy (&outputTensorMeta, &prop->output_meta);
+
+  return loadScript ();
+}
+
+/**
+ * @brief      get the script path
+ * @return the script path.
+ */
+const char *
+PYCore::getScriptPath ()
+{
+  return script_path.c_str();
+}
+
+/**
+ * @brief      load the py script
+ * @note       the script will be loaded
+ * @return 0 if OK. non-zero if error.
+ *        -1 if the py file is not loaded.
+ *        -2 if the target class is not found.
+ *        -3 if the class is not created.
+ */
+int
+PYCore::loadScript ()
+{
+#if (DBG)
+  gint64 start_time = g_get_real_time ();
+#endif
+
+  PyObject *module = PyImport_ImportModule(module_name.c_str());
+  if (module) {
+    PyObject *cls = PyObject_GetAttrString(module, "CustomFilter");
+    if (cls) {
+      PyObject *py_args;
+      if (!module_args.empty()) {
+        gchar **g_args = g_strsplit(module_args.c_str(), " ", 0);
+        char **args = g_args;
+        int argc = 0;
+        while (*(args++) != NULL) argc++;
+
+        g_assert(argc > 0);
+          
+        py_args = PyTuple_New(argc);
+
+        for (int i = 0; i < argc; i++)
+          PyTuple_SetItem(py_args, i, PyUnicode_FromString(g_args[i]));
+
+        core_obj = PyObject_CallObject(cls, py_args);
+
+        Py_XDECREF(py_args);
+        g_strfreev(g_args);
+      } else 
+        core_obj = PyObject_CallObject(cls, NULL);
+
+      if (core_obj) {
+        /** check whther either setInputDim or getInputDim/getOutputDim are defined */
+        if (PyObject_HasAttrString(core_obj, (char*) "setInputDim"))
+          callback_type = CB_SETDIM;
+        else if (PyObject_HasAttrString(core_obj, (char*) "getInputDim") && 
+                 PyObject_HasAttrString(core_obj, (char*) "getOutputDim"))
+          callback_type = CB_GETDIM;
+        else
+          callback_type = CB_END;
+      } else {
+        Py_ERRMSG ("Fail to create an instance 'CustomFilter'\n");
+        return -3;
+      }
+
+      Py_XDECREF(cls);
+    } else {
+      Py_ERRMSG ("Cannot find 'CustomFilter' class in the script\n");
+      return -2;
+    }
+
+    Py_XDECREF(module);
+  } else {
+    Py_ERRMSG ("the script is not properly loaded\n");
+    return -1;
+  }
+
+  configured = true;
+
+#if (DBG)
+  gint64 stop_time = g_get_real_time ();
+  g_message ("Script is loaded: %" G_GINT64_FORMAT, (stop_time - start_time));
+#endif
+  
+  return 0;
+}
+
+/**
+ * @brief      check the data type of tensors in array
+ * @param output : tensor memory for output tensors
+ * @param array  : python array
+ * @return a boolean value for whether the types are matched
+ */
+int
+PYCore::checkTensorType (GstTensorMemory *output, PyArrayObject *array)
+{
+  g_assert (output);
+  g_assert (array);
+
+  int nns_type = output->type;
+  int np_type = PyArray_TYPE(array);
+
+  switch (nns_type) {
+    case _NNS_INT64: return np_type == NPY_INT64;
+    case _NNS_UINT64: return np_type == NPY_UINT64;
+    case _NNS_INT32: return np_type == NPY_INT32;
+    case _NNS_UINT32: return np_type == NPY_UINT32;
+    case _NNS_INT16: return np_type == NPY_INT16;
+    case _NNS_UINT16: return np_type == NPY_UINT16;
+    case _NNS_INT8: return np_type == NPY_INT8;
+    case _NNS_UINT8: return np_type == NPY_UINT8;
+    case _NNS_FLOAT64: return np_type == NPY_FLOAT64;
+    case _NNS_FLOAT32: return np_type == NPY_FLOAT32;
+  }
+
+  return 0;
+}
+
+/**
+ * @brief      check the data size of tensors in array
+ * @param output : tensor memory for output tensors
+ * @param array  : python array
+ * @return a boolean value for whether the sizes are matched
+ */
+int
+PYCore::checkTensorSize (GstTensorMemory *output, PyArrayObject *array)
+{
+  g_assert (output);
+  g_assert (array);
+
+  size_t total_size = PyArray_ITEMSIZE(array);
+
+  for (int i = 0; i < PyArray_NDIM(array); i++) 
+    total_size *= PyArray_DIM(array, i);
+
+  return (output->size == total_size);
+}
+
+/**
+ * @brief      return the Dimension of Input Tensor.
+ * @param[out] info Structure for tensor info.
+ * @todo return whole array rather than index 0
+ * @return 0 if OK. non-zero if error.
+ */
+int
+PYCore::getInputTensorDim (GstTensorsInfo * info)
+{
+  int res = 0;
+
+  g_assert (info);
+
+  Py_LOCK();
+
+  PyObject *result = PyObject_CallMethod(core_obj, (char*) "getInputDim", NULL);
+  if (result) {
+    parseOutputTensors(result, info);
+    Py_XDECREF(result);
+  } else { 
+    Py_ERRMSG("Fail to call 'getInputDim'");
+    res = -1;
+  }
+
+  Py_UNLOCK();
+
+  return res;
+}
+
+/**
+ * @brief      return the Dimension of Tensor.
+ * @param[out] info Structure for tensor info.
+ * @todo return whole array rather than index 0
+ * @return 0 if OK. non-zero if error.
+ */
+int
+PYCore::getOutputTensorDim (GstTensorsInfo * info)
+{
+  int res = 0;
+  
+  g_assert (info);
+
+  Py_LOCK();
+
+  PyObject *result = PyObject_CallMethod(core_obj, (char*) "getOutputDim", NULL);
+  if (result) {
+    parseOutputTensors(result, info);
+    Py_XDECREF(result);
+  } else { 
+    Py_ERRMSG("Fail to call 'getOutputDim'");
+    res = -1;
+  }
+
+  Py_UNLOCK();
+
+  return res;
+}
+
+/**
+ * @brief      set the Dimension of Input Tensor and return the Dimension of Input Tensor.
+ * @param[in] info Structure for input tensor info.
+ * @param[out] info Structure for output tensor info.
+ * @todo return whole array rather than index 0
+ * @return 0 if OK. non-zero if error.
+ */
+int
+PYCore::setInputTensorDim (const GstTensorsInfo * in_info, GstTensorsInfo * out_info)
+{
+  int res = 0;
+
+  g_assert (in_info);
+  g_assert (out_info);
+
+  Py_LOCK();
+
+  /** to Python list object */
+  PyObject *param = PyList_New(0);
+  g_assert (param);
+
+  inputTensorMeta.num_tensors = in_info->num_tensors;
+  for (int i = 0; i < in_info->num_tensors; i++) {
+    PyObject *shape = PyTensorShape_New (&in_info->info[i]);
+    assert (shape);
+    
+    PyList_Append(param, shape);
+  }
+
+  PyObject *result = PyObject_CallMethod(
+      core_obj, (char*) "setInputDim", (char*) "(O)", param);
+
+  /** dereference input param */
+  for (int i = 0; i < in_info->num_tensors; i++) {
+    PyObject *shape = PyList_GetItem(param, (Py_ssize_t) i);
+    Py_XDECREF (shape);
+  }
+  Py_XDECREF (param);
+
+  if (result) {
+    parseOutputTensors(result, out_info);
+    outputTensorMeta.num_tensors = out_info->num_tensors;
+    Py_XDECREF(result);
+  } else { 
+    Py_ERRMSG("Fail to call 'setInputDim'");
+    res = -1;
+  }
+
+  Py_UNLOCK();
+
+  return res;
+}
+
+/**
+ * @brief      allocate TensorShape object
+ * @param info : the tensor info
+ * @return created object
+ */
+PyObject* 
+PYCore::PyTensorShape_New (const GstTensorInfo* info)
+{
+  PyObject *args = PyTuple_New(2);
+  PyObject *dims = PyList_New(0);
+  PyObject *type = (PyObject*) PyArray_DescrFromType(getNumpyType(info->type));
+
+  g_assert(args);
+  g_assert(dims);
+  g_assert(type);
+
+  for (int i = 0; i < NNS_TENSOR_RANK_LIMIT; i++) 
+    PyList_Append(dims, PyLong_FromLong((uint64_t) info->dimension[i]));
+
+  PyTuple_SetItem(args, 0, dims);
+  PyTuple_SetItem(args, 1, type);
+  PyObject *obj = PyObject_CallObject(shape_cls, args);
+  g_assert(obj);
+
+  return obj;
+}
+
+/**
+ * @brief parse the invoke result to feed output tensors
+ * @param[result] Python object retunred by invoke
+ * @param[info] info Structure for output tensor info
+ * @return None
+ */
+void
+PYCore::parseOutputTensors(PyObject* result, GstTensorsInfo * info)
+{
+  info->num_tensors = PyList_Size(result);
+  for (int i = 0; i < info->num_tensors; i++) {
+    /** don't own the reference */
+    PyObject *tensor_shape = PyList_GetItem(result, (Py_ssize_t) i); 
+    g_assert(tensor_shape);
+
+    PyObject *shape_dims = PyObject_CallMethod(tensor_shape, (char*) "getDims", NULL);
+    g_assert(shape_dims);
+
+    PyObject *shape_type = PyObject_CallMethod(tensor_shape, (char*) "getType", NULL);
+    g_assert(shape_type);
+
+    /** convert numpy type to tensor type */
+    info->info[i].type = getTensorType((NPY_TYPES)(((PyArray_Descr*) shape_type)->type_num));
+    for (int j = 0; j < PyList_Size(shape_dims); j++)
+      info->info[i].dimension[j] = 
+        (uint32_t) PyLong_AsLong(PyList_GetItem(shape_dims, (Py_ssize_t) j));
+
+    Py_XDECREF (shape_dims);
+    Py_XDECREF (shape_type);
+  }
+}
+
+/**
+ * @brief      run the script with the input.
+ * @param[in] input : The array of input tensors
+ * @param[out]  output : The array of output tensors
+ * @return 0 if OK. non-zero if error.
+ *        -1 if the script does not work properly.
+ *        -2 if the output properties are different with script.
+ */
+int
+PYCore::run (const GstTensorMemory * input, GstTensorMemory * output)
+{
+  int res = 0;
+
+#if (DBG)
+  gint64 start_time = g_get_real_time ();
+#endif
+
+  g_assert(input);
+  g_assert(output);
+
+  Py_LOCK();
+
+  PyObject *param = PyList_New(0);
+  for (int i = 0; i < inputTensorMeta.num_tensors; i++) {
+    /** create a Numpy array wrapper (1-D) for NNS tensor data */
+    npy_intp input_dims[] = {(npy_intp) input[i].size};
+    PyObject *input_array = PyArray_SimpleNewFromData(
+        1, input_dims, getNumpyType(input[i].type), input[i].data);
+    PyList_Append(param, input_array);
+  }
+
+  PyObject *result = PyObject_CallMethod(core_obj, (char*) "invoke", (char*) "(O)", param);
+  if (result) {
+    g_assert(PyList_Size(result) == outputTensorMeta.num_tensors);
+
+    for (int i = 0; i < outputTensorMeta.num_tensors; i++) {
+      PyArrayObject* output_array = (PyArrayObject*) PyList_GetItem(result, (Py_ssize_t) i);
+      /** type/size checking */
+      if (checkTensorType(&output[i], output_array) && 
+          checkTensorSize(&output[i], output_array)) { 
+        /** obtain the pointer to the buffer for the output array */
+        output[i].data = PyArray_DATA(output_array);
+        Py_XINCREF(output_array);
+        outputArrayMap.insert (std::make_pair (output[i].data, output_array));
+      } else {
+        g_critical ("Output tensor type/size is not matched\n");
+        res = -2;
+        break;
+      }
+    }
+
+    Py_XDECREF(result);
+  } else {
+    Py_ERRMSG("Fail to call 'invoke'");
+    res = -1;
+  }
+
+  /** dereference input param */
+  for (int i = 0; i < inputTensorMeta.num_tensors; i++) {
+    PyObject *input_array = PyList_GetItem(param, (Py_ssize_t) i);
+    Py_XDECREF (input_array);
+  }
+  Py_XDECREF (param);
+
+  Py_UNLOCK();
+
+#if (DBG)
+  gint64 stop_time = g_get_real_time ();
+  g_message ("Invoke() is finished: %" G_GINT64_FORMAT,
+      (stop_time - start_time));
+#endif
+
+  return res;
+}
+
+/**
+ * @brief      return the data type of the tensor
+ * @param npyType      : the defined type of Python numpy
+ * @return the enum of defined _NNS_TYPE
+ */
+tensor_type
+PYCore::getTensorType (NPY_TYPES npyType)
+{
+  switch (npyType) {
+    case NPY_INT32:
+      return _NNS_INT32;
+    case NPY_UINT32:
+      return _NNS_UINT32;
+    case NPY_INT16:
+      return _NNS_INT16;
+    case NPY_UINT16:
+      return _NNS_UINT16;
+    case NPY_INT8:
+      return _NNS_INT8;
+    case NPY_UINT8:
+      return _NNS_UINT8;
+    case NPY_INT64:
+      return _NNS_INT64;
+    case NPY_UINT64:
+      return _NNS_UINT64;
+    case NPY_FLOAT32:
+      return _NNS_FLOAT32;
+    case NPY_FLOAT64:
+      return _NNS_FLOAT64;
+    default:
+      /** @todo Support other types */
+      break;
+  }
+
+  return _NNS_END;
+}
+
+/**
+ * @brief      return the data type of the tensor for Python numpy
+ * @param tType        : the defined type of NNStreamer
+ * @return the enum of defined numpy datatypes
+ */
+NPY_TYPES
+PYCore::getNumpyType (tensor_type tType)
+{
+  switch (tType) {
+    case _NNS_INT32:
+      return NPY_INT32;
+    case _NNS_UINT32:
+      return NPY_UINT32;
+    case _NNS_INT16:
+      return NPY_INT16;
+    case _NNS_UINT16:
+      return NPY_UINT16;
+    case _NNS_INT8:
+      return NPY_INT8;
+    case _NNS_UINT8:
+      return NPY_UINT8;
+    case _NNS_INT64:
+      return NPY_INT64;
+    case _NNS_UINT64:
+      return NPY_UINT64;
+    case _NNS_FLOAT32:
+      return NPY_FLOAT32;
+    case _NNS_FLOAT64:
+      return NPY_FLOAT64;
+    default:
+      /** @todo Support other types */
+      break;
+  }
+  return NPY_NOTYPE;
+}
+
+/**
+ * @brief allocate new PYCore class.
+ * @param script_path : python script path
+ * @return  Nothing
+ */
+void *
+py_core_new (const char * _script_path, const char * _custom)
+{
+  return new PYCore (_script_path, _custom != NULL ? _custom : "");
+}
+
+/**
+ * @brief      delete the PYCore class.
+ * @param      py      : the class object
+ * @return     Nothing
+ */
+void
+py_core_delete (void * py)
+{
+  PYCore *c = (PYCore *) py;
+  delete c;
+}
+
+/**
+ * @brief      initialize the object with py script
+ * @param      py      : the class object
+ * @return 0 if OK. non-zero if error.
+ */
+int
+py_core_init (void * py, const GstTensorFilterProperties * prop)
+{
+  PYCore *c = (PYCore *) py;
+  return c->init (prop);
+}
+
+/**
+ * @brief      get script path
+ * @param      py      : the class object
+ * @return     script path
+ */
+const char *
+py_core_getScriptPath (void * py)
+{
+  PYCore *c = (PYCore *) py;
+  return c->getScriptPath ();
+}
+
+/**
+ * @brief      get the Dimension of Input Tensor of script
+ * @param      py      the class object
+ * @param[out] info Structure for tensor info.
+ * @return 0 if OK. non-zero if error.
+ */
+int
+py_core_getInputDim (void * py, GstTensorsInfo * info)
+{
+  PYCore *c = (PYCore *) py;
+  return c->getInputTensorDim (info);
+}
+
+/**
+ * @brief      get the Dimension of Output Tensor of script
+ * @param      py      the class object
+ * @param[out] info Structure for tensor info.
+ * @return 0 if OK. non-zero if error.
+ */
+int
+py_core_getOutputDim (void * py, GstTensorsInfo * info)
+{
+  PYCore *c = (PYCore *) py;
+  return c->getOutputTensorDim (info);
+}
+
+/**
+ * @brief      set the Dimension of Input Tensor and return the Dimension of Output Tensor
+ * @param      py      the class object
+ * @param[in] info Structure for input tensor info.
+ * @param[out] info Structure for output tensor info.
+ * @return 0 if OK. non-zero if error.
+ */
+int
+py_core_setInputDim (void * py, const GstTensorsInfo * in_info, GstTensorsInfo * out_info)
+{
+  PYCore *c = (PYCore *) py;
+  return c->setInputTensorDim (in_info, out_info);
+}
+
+/**
+ * @brief      run the script
+ * @param      py      : the class object
+ * @param[in] input : The array of input tensors
+ * @param[out]  output : The array of output tensors
+ * @return 0 if OK. non-zero if error.
+ */
+int
+py_core_run (void * py, const GstTensorMemory * input, GstTensorMemory * output)
+{
+  PYCore *c = (PYCore *) py;
+  return c->run (input, output);
+}
+
+/**
+ * @brief      the destroy notify method for Python. it will free the output array buffer
+ * @param[in] data : the data element destroyed at the pipeline
+ */
+void
+py_core_destroyNotify (void * data)
+{
+  std::map <void*, PyArrayObject*>::iterator it;
+  it = PYCore::outputArrayMap.find(data);
+  if (it != PYCore::outputArrayMap.end()){
+    Py_XDECREF(it->second);
+    PYCore::outputArrayMap.erase (it);
+  } else
+    g_critical("Cannot find output data: 0x%lx", (unsigned long) data);
+}
+
+/**
+ * @brief return this module's callback type
+ * @param None
+ * @return callback type
+ */
+cb_type
+py_core_getCbType (void * py)
+{
+  PYCore *c = (PYCore *) py;
+  return c->getCbType ();
+}
diff --git a/ext/nnstreamer/tensor_filter/tensor_filter_python_core.h b/ext/nnstreamer/tensor_filter/tensor_filter_python_core.h
new file mode 100644 (file)
index 0000000..b670344
--- /dev/null
@@ -0,0 +1,135 @@
+/**
+ * Copyright (C) 2019 Samsung Electronics Co., Ltd. All rights reserved.
+ * Copyright (C) 2019 Dongju Chae <dongju.chae@samsung.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ */
+/**
+ * @file   tensor_filter_python_core.h
+ * @author Dongju Chae <dongju.chae@samsung.com>
+ * @date   03/29/2019
+ * @brief  connection with python libraries.
+ *
+ * @bug     No known bugs.
+ */
+#ifndef TENSOR_FILTER_PYTHON_CORE_H
+#define TENSOR_FILTER_PYTHON_CORE_H
+
+#include <Python.h>
+#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
+#include <numpy/arrayobject.h>
+
+#include <tensor_typedef.h>
+#include <glib.h>
+#include <gst/gst.h>
+#include <string.h>
+
+#include <dlfcn.h>
+#include <pthread.h>
+#include <structmember.h>
+
+#define Py_ERRMSG(...) do {PyErr_Print(); g_critical (__VA_ARGS__);} while (0);
+
+/** @brief Callback type for custom filter */
+typedef enum _cb_type
+{
+  CB_SETDIM = 0,
+  CB_GETDIM,
+
+  CB_END,
+} cb_type;
+
+#ifdef __cplusplus
+#include <vector>
+#include <map>
+
+/**
+ * @brief      Python embedding core structure
+ */
+class PYCore
+{
+public:
+  /**
+   * member functions.
+   */
+  PYCore (const char* _script_path, const char* _custom);
+  ~PYCore ();
+
+  int init (const GstTensorFilterProperties * prop);
+  int loadScript ();
+  const char* getScriptPath();
+  int getInputTensorDim (GstTensorsInfo * info);
+  int getOutputTensorDim (GstTensorsInfo * info);
+  int setInputTensorDim (const GstTensorsInfo * in_info, GstTensorsInfo * out_info);
+  int run (const GstTensorMemory * input, GstTensorMemory * output);
+
+  void parseOutputTensors(PyObject* result, GstTensorsInfo * info);
+
+  /** @brief Return callback type */
+  cb_type getCbType () { return callback_type; }
+  /** @brief Lock python-related actions */
+  void Py_LOCK() { pthread_mutex_lock(&py_mutex); }
+  /** @brief Unlock python-related actions */
+  void Py_UNLOCK() { pthread_mutex_unlock(&py_mutex); }
+
+  PyObject* PyTensorShape_New (const GstTensorInfo *info);
+
+  int checkTensorType (GstTensorMemory *output, PyArrayObject *array);
+  int checkTensorSize (GstTensorMemory *output, PyArrayObject *array);
+
+  tensor_type getTensorType (NPY_TYPES npyType);
+  NPY_TYPES getNumpyType (tensor_type tType);
+
+  static std::map <void*, PyArrayObject*> outputArrayMap;
+private:
+
+  const std::string script_path;  /**< from model_path property */
+  const std::string module_args;  /**< from custom property */
+
+  std::string module_name;
+
+  cb_type callback_type;
+
+  PyObject* core_obj;
+  PyObject* shape_cls;
+  pthread_mutex_t py_mutex;
+
+  GstTensorsInfo inputTensorMeta;  /**< The tensor info of input tensors */
+  GstTensorsInfo outputTensorMeta;  /**< The tensor info of output tensors */
+
+  bool configured; /**< True if the script is successfully loaded */
+  void *handle; /**< returned handle by dlopen() */
+};
+
+/**
+ * @brief      the definition of functions to be used at C files.
+ */
+extern "C"
+{
+#endif
+  void *py_core_new (const char *_script_path, const char *_custom_path);
+  void py_core_delete (void * py);
+  int py_core_init (void * py, const GstTensorFilterProperties * prop);
+  const char *py_core_getScriptPath (void * py);
+  int py_core_getInputDim (void * py, GstTensorsInfo * info);
+  int py_core_getOutputDim (void * py, GstTensorsInfo * info);
+  int py_core_setInputDim (void * py, const GstTensorsInfo * in_info, GstTensorsInfo * out_info);
+  int py_core_run (void * py, const GstTensorMemory * input,
+      GstTensorMemory * output);
+  void py_core_destroyNotify (void * data);
+  cb_type py_core_getCbType (void * py);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TENSOR_FILTER_PYTHON_CORE_H */