LCOV - code coverage report
Current view: top level - nnstreamer-2.4.2/ext/nnstreamer/tensor_converter - tensor_converter_python3.cc (source / functions) Coverage Total Hit
Test: nnstreamer 2.4.2-0 nnstreamer/nnstreamer#eca68b8d050408568af95d831a8eef62aaee7784 Lines: 86.2 % 159 137
Test Date: 2025-03-13 05:38:21 Functions: 100.0 % 14 14

            Line data    Source code
       1              : /* SPDX-License-Identifier: LGPL-2.1-only */
       2              : /**
       3              :  * GStreamer / NNStreamer tensor_converter subplugin, "python3"
       4              :  * Copyright (C) 2021 Gichan Jang <gichan2.jang@samsung.com>
       5              :  */
       6              : /**
       7              :  * @file        tensor_converter_python3.c
       8              :  * @date        May 03 2021
       9              :  * @brief       NNStreamer tensor-converter subplugin, "python3",
      10              :  *         which converts to tensors using python.
      11              :  * @see         https://github.com/nnstreamer/nnstreamer
      12              :  * @author      Gichan Jang <gichan2.jang@samsung.com>
      13              :  * @bug         python converter with Python3.9.10 is stuck during Py_Finalize().
      14              :  */
      15              : 
      16              : #include <nnstreamer_plugin_api.h>
      17              : #include <nnstreamer_plugin_api_converter.h>
      18              : #include <nnstreamer_util.h>
      19              : #include "nnstreamer_python3_helper.h"
      20              : 
      21              : #ifdef __cplusplus
      22              : extern "C" {
      23              : #endif /* __cplusplus */
      24              : void init_converter_py (void) __attribute__ ((constructor));
      25              : void fini_converter_py (void) __attribute__ ((destructor));
      26              : #ifdef __cplusplus
      27              : }
      28              : #endif /* __cplusplus */
      29              : 
      30              : /**
      31              :  * @brief       Python embedding core structure
      32              :  */
      33              : class PYConverterCore
      34              : {
      35              :   public:
      36              :   /**
      37              :    * member functions.
      38              :    */
      39              :   PYConverterCore (const char *_script_path);
      40              :   ~PYConverterCore ();
      41              : 
      42              :   int init ();
      43              :   const char *getScriptPath ();
      44              :   GstBuffer *convert (GstBuffer *in_buf, GstTensorsConfig *config);
      45              : 
      46              :   /** @brief Lock python-related actions */
      47           26 :   void Py_LOCK ()
      48              :   {
      49           26 :     g_mutex_lock (&py_mutex);
      50           26 :   }
      51              :   /** @brief Unlock python-related actions */
      52           26 :   void Py_UNLOCK ()
      53              :   {
      54           26 :     g_mutex_unlock (&py_mutex);
      55           26 :   }
      56              : 
      57              :   private:
      58              :   std::string module_name;
      59              :   const std::string script_path;
      60              :   PyObject *shape_cls;
      61              :   PyObject *core_obj;
      62              :   void *handle; /**< returned handle by dlopen() */
      63              :   GMutex py_mutex;
      64              : };
      65              : 
      66              : /**
      67              :  * @brief       PYConverterCore creator
      68              :  * @param       _script_path    : the logical path to '{script_name}.py' file
      69              :  * @note        the script of _script_path will be loaded simultaneously
      70              :  * @return      Nothing
      71              :  */
      72           23 : PYConverterCore::PYConverterCore (const char *_script_path)
      73           46 :     : script_path (_script_path)
      74              : {
      75           23 :   if (openPythonLib (&handle))
      76            0 :     throw std::runtime_error (dlerror ());
      77              : 
      78           23 :   _import_array (); /** for numpy */
      79              : 
      80              :   /**
      81              :    * Parse script path to get module name
      82              :    * The module name should drop its extension (i.e., .py)
      83              :    */
      84           23 :   module_name = script_path;
      85           23 :   const size_t last_idx = module_name.find_last_of ("/");
      86              : 
      87           23 :   if (last_idx != std::string::npos)
      88           23 :     module_name.erase (0, last_idx + 1);
      89              : 
      90           23 :   const size_t ext_idx = module_name.rfind ('.');
      91           23 :   if (ext_idx != std::string::npos)
      92           23 :     module_name.erase (ext_idx);
      93              : 
      94           23 :   addToSysPath (script_path.substr (0, last_idx).c_str ());
      95              : 
      96           23 :   core_obj = NULL;
      97           23 :   shape_cls = NULL;
      98              : 
      99           23 :   g_mutex_init (&py_mutex);
     100           23 : }
     101              : 
     102              : /**
     103              :  * @brief       PYConverterCore Destructor
     104              :  * @return      Nothing
     105              :  */
     106           46 : PYConverterCore::~PYConverterCore ()
     107              : {
     108           23 :   Py_SAFEDECREF (core_obj);
     109           23 :   Py_SAFEDECREF (shape_cls);
     110           23 :   PyErr_Clear ();
     111              : 
     112           23 :   dlclose (handle);
     113           23 :   g_mutex_clear (&py_mutex);
     114           23 : }
     115              : 
     116              : /**
     117              :  * @brief       convert any media stream to tensor
     118              :  */
     119              : GstBuffer *
     120           26 : PYConverterCore::convert (GstBuffer *in_buf, GstTensorsConfig *config)
     121              : {
     122              :   GstMemory *in_mem[NNS_TENSOR_SIZE_LIMIT], *out_mem;
     123              :   GstMapInfo in_info[NNS_TENSOR_SIZE_LIMIT];
     124           26 :   GstBuffer *out_buf = NULL;
     125              :   PyObject *tensors_info, *output, *pyValue, *param;
     126              :   gint rate_n, rate_d;
     127              :   guint i, num;
     128              :   gsize mem_size;
     129              :   gpointer mem_data;
     130              : 
     131           26 :   if (nullptr == in_buf)
     132            0 :     throw std::invalid_argument ("Null pointers are given to PYConverterCore::convert().\n");
     133           26 :   num = gst_tensor_buffer_get_count (in_buf);
     134           26 :   tensors_info = output = pyValue = param = nullptr;
     135              : 
     136           26 :   Py_LOCK ();
     137           26 :   param = PyList_New (num);
     138              : 
     139           52 :   for (i = 0; i < num; i++) {
     140           26 :     in_mem[i] = gst_tensor_buffer_get_nth_memory (in_buf, i);
     141              : 
     142           26 :     if (!gst_memory_map (in_mem[i], &in_info[i], GST_MAP_READ)) {
     143            0 :       Py_ERRMSG ("Cannot map input memory / tensor_converter::custom-script");
     144            0 :       num = i;
     145            0 :       goto done;
     146              :     }
     147              : 
     148           26 :     npy_intp input_dims[] = { (npy_intp) (in_info[i].size) };
     149              :     PyObject *input_array
     150           26 :         = PyArray_SimpleNewFromData (1, input_dims, NPY_UINT8, in_info[i].data);
     151              : 
     152           26 :     PyList_SetItem (param, i, input_array);
     153              :   }
     154              : 
     155           26 :   if (!PyObject_HasAttrString (core_obj, (char *) "convert")) {
     156            0 :     Py_ERRMSG ("Cannot find 'convert'");
     157            0 :     goto done;
     158              :   }
     159              : 
     160           26 :   pyValue = PyObject_CallMethod (core_obj, "convert", "(O)", param);
     161              : 
     162           26 :   if (!PyArg_ParseTuple (pyValue, "OOii", &tensors_info, &output, &rate_n, &rate_d)) {
     163            0 :     Py_ERRMSG ("Failed to parse converting result");
     164            0 :     goto done;
     165              :   }
     166              : 
     167           26 :   if (parseTensorsInfo (tensors_info, &config->info) != 0) {
     168            0 :     Py_ERRMSG ("Failed to parse tensors info");
     169            0 :     goto done;
     170              :   }
     171           26 :   config->rate_n = rate_n;
     172           26 :   config->rate_d = rate_d;
     173              : 
     174           26 :   if (output) {
     175              :     GstTensorInfo *_info;
     176           26 :     Py_ssize_t num_tensors = PyList_Size (output);
     177              : 
     178           26 :     if (num_tensors < 0 || num_tensors > NNS_TENSOR_SIZE_LIMIT) {
     179            0 :       Py_ERRMSG ("Fail to get output from 'convert', invalid output size.");
     180            0 :       goto done;
     181              :     }
     182              : 
     183           26 :     out_buf = gst_buffer_new ();
     184           74 :     for (i = 0; i < (guint) num_tensors; i++) {
     185              :       PyArrayObject *output_array
     186           48 :           = (PyArrayObject *) PyList_GetItem (output, (Py_ssize_t) i);
     187              : 
     188           48 :       _info = gst_tensors_info_get_nth_info (&config->info, i);
     189           48 :       mem_size = PyArray_SIZE (output_array);
     190           48 :       mem_data = _g_memdup ((guint8 *) PyArray_DATA (output_array), mem_size);
     191              : 
     192           48 :       out_mem = gst_memory_new_wrapped (
     193              :           (GstMemoryFlags) 0, mem_data, mem_size, 0, mem_size, mem_data, g_free);
     194              : 
     195           48 :       gst_tensor_buffer_append_memory (out_buf, out_mem, _info);
     196              :     }
     197              :   } else {
     198            0 :     Py_ERRMSG ("Fail to get output from 'convert'");
     199              :   }
     200              : 
     201           26 : done:
     202           52 :   for (i = 0; i < num; i++) {
     203           26 :     gst_memory_unmap (in_mem[i], &in_info[i]);
     204           26 :     gst_memory_unref (in_mem[i]);
     205              :   }
     206              : 
     207           26 :   Py_SAFEDECREF (param);
     208           26 :   Py_SAFEDECREF (pyValue);
     209              : 
     210           26 :   Py_UNLOCK ();
     211           26 :   return out_buf;
     212              : }
     213              : 
     214              : /**
     215              :  * @brief       initialize the object with python script
     216              :  * @return 0 if OK. non-zero if error.
     217              :  */
     218              : int
     219           23 : PYConverterCore::init ()
     220              : {
     221              :   /** Find nnstreamer_api module */
     222           23 :   PyObject *api_module = PyImport_ImportModule ("nnstreamer_python");
     223           23 :   if (api_module == NULL)
     224            0 :     return -EINVAL;
     225              : 
     226           23 :   shape_cls = PyObject_GetAttrString (api_module, "TensorShape");
     227           23 :   Py_SAFEDECREF (api_module);
     228              : 
     229           23 :   if (shape_cls == NULL)
     230            0 :     return -EINVAL;
     231              : 
     232           23 :   return loadScript (&core_obj, module_name.c_str (), "CustomConverter");
     233              : }
     234              : 
     235              : /**
     236              :  * @brief       get the script path
     237              :  * @return the script path.
     238              :  */
     239              : const char *
     240            2 : PYConverterCore::getScriptPath ()
     241              : {
     242            2 :   return script_path.c_str ();
     243              : }
     244              : 
     245              : /**
     246              :  * @brief Free privateData and move on.
     247              :  */
     248              : static void
     249           16 : py_close (void **private_data)
     250              : {
     251           16 :   PYConverterCore *core = static_cast<PYConverterCore *> (*private_data);
     252              : 
     253           16 :   g_return_if_fail (core != NULL);
     254              : 
     255           16 :   PyGILState_STATE gstate = PyGILState_Ensure ();
     256           16 :   delete core;
     257           16 :   PyGILState_Release (gstate);
     258              : 
     259           16 :   *private_data = NULL;
     260              : }
     261              : 
     262              : /**
     263              :  * @brief The open callback for GstTensorConverterFramework. Called before anything else
     264              :  * @param path: python script path
     265              :  * @param private_data: python plugin's private data
     266              :  */
     267              : static int
     268           24 : py_open (const gchar *path, void **priv_data)
     269              : {
     270           24 :   int ret = 0;
     271              :   PYConverterCore *core;
     272              :   PyGILState_STATE gstate;
     273              : 
     274           24 :   if (!Py_IsInitialized ())
     275            0 :     throw std::runtime_error ("Python is not initialize.");
     276              : 
     277              :   /** Load python script file */
     278           24 :   core = static_cast<PYConverterCore *> (*priv_data);
     279              : 
     280           24 :   if (core != NULL) {
     281            2 :     if (g_strcmp0 (path, core->getScriptPath ()) == 0)
     282            1 :       return 1; /* skipped */
     283              : 
     284            1 :     py_close (priv_data);
     285              :   }
     286              : 
     287              :   /* init null */
     288           23 :   *priv_data = NULL;
     289              : 
     290           23 :   gstate = PyGILState_Ensure ();
     291           23 :   core = new PYConverterCore (path);
     292           23 :   if (core == NULL) {
     293            0 :     Py_ERRMSG ("Failed to allocate memory for converter subplugin or path invalid: Python\n");
     294            0 :     ret = -ENOMEM;
     295            0 :     goto done;
     296              :   }
     297              : 
     298           23 :   if (core->init () != 0) {
     299            7 :     delete core;
     300            7 :     Py_ERRMSG ("failed to initialize the object or the python script is invalid: Python\n");
     301            7 :     ret = -EINVAL;
     302            7 :     goto done;
     303              :   }
     304              : 
     305           16 :   *priv_data = core;
     306           23 : done:
     307           23 :   PyGILState_Release (gstate);
     308           23 :   return ret;
     309              : }
     310              : 
     311              : /** @brief tensor converter plugin's NNStreamerExternalConverter callback */
     312              : static GstCaps *
     313          458 : python_query_caps (const GstTensorsConfig *config)
     314              : {
     315              :   UNUSED (config);
     316          458 :   return gst_caps_from_string ("application/octet-stream");
     317              : }
     318              : 
     319              : /**
     320              :  * @brief tensor converter plugin's NNStreamerExternalConverter callback
     321              :  */
     322              : static gboolean
     323           20 : python_get_out_config (const GstCaps *in_cap, GstTensorsConfig *config)
     324              : {
     325              :   GstStructure *structure;
     326           20 :   g_return_val_if_fail (config != NULL, FALSE);
     327           19 :   gst_tensors_config_init (config);
     328           19 :   g_return_val_if_fail (in_cap != NULL, FALSE);
     329              : 
     330           18 :   structure = gst_caps_get_structure (in_cap, 0);
     331           18 :   g_return_val_if_fail (structure != NULL, FALSE);
     332              : 
     333              :   /* All tensor info should be updated later in chain function. */
     334           18 :   config->info.info[0].type = _NNS_UINT8;
     335           18 :   config->info.num_tensors = 1;
     336           18 :   if (gst_tensor_parse_dimension ("1:1:1:1", config->info.info[0].dimension) == 0) {
     337            0 :     Py_ERRMSG ("Failed to set initial dimension for subplugin");
     338            0 :     return FALSE;
     339              :   }
     340              : 
     341           18 :   if (gst_structure_has_field (structure, "framerate")) {
     342           10 :     gst_structure_get_fraction (structure, "framerate", &config->rate_n, &config->rate_d);
     343              :   } else {
     344              :     /* cannot get the framerate */
     345            8 :     config->rate_n = 0;
     346            8 :     config->rate_d = 1;
     347              :   }
     348           18 :   return TRUE;
     349              : }
     350              : 
     351              : /**
     352              :  * @brief tensor converter plugin's NNStreamerExternalConverter callback
     353              :  */
     354              : static GstBuffer *
     355           28 : python_convert (GstBuffer *in_buf, GstTensorsConfig *config, void *priv_data)
     356              : {
     357              :   GstBuffer *ret;
     358           28 :   PYConverterCore *core = static_cast<PYConverterCore *> (priv_data);
     359              :   PyGILState_STATE gstate;
     360           28 :   g_return_val_if_fail (in_buf, NULL);
     361           27 :   g_return_val_if_fail (config, NULL);
     362              : 
     363           26 :   gstate = PyGILState_Ensure ();
     364           26 :   ret = core->convert (in_buf, config);
     365           26 :   PyGILState_Release (gstate);
     366           26 :   return ret;
     367              : }
     368              : 
     369              : static const gchar converter_subplugin_python[] = "python3";
     370              : 
     371              : /** @brief flatbuffer tensor converter sub-plugin NNStreamerExternalConverter instance */
     372              : static NNStreamerExternalConverter Python = {
     373              :   .name = converter_subplugin_python,
     374              :   .convert = python_convert,
     375              :   .get_out_config = python_get_out_config,
     376              :   .query_caps = python_query_caps,
     377              :   .open = py_open,
     378              :   .close = py_close,
     379              : };
     380              : 
     381              : #ifdef __cplusplus
     382              : extern "C" {
     383              : #endif /* __cplusplus */
     384              : /** @brief Initialize this object for tensor converter sub-plugin */
     385              : void
     386          431 : init_converter_py (void)
     387              : {
     388              :   /** Python should be initialized and finalized only once */
     389          431 :   nnstreamer_python_init_refcnt ();
     390          431 :   registerExternalConverter (&Python);
     391          431 : }
     392              : 
     393              : /** @brief Destruct this object for tensor converter sub-plugin */
     394              : void
     395          431 : fini_converter_py (void)
     396              : {
     397          431 :   nnstreamer_python_status_check ();
     398          431 :   nnstreamer_python_fini_refcnt ();
     399          431 :   unregisterExternalConverter (Python.name);
     400              : /**
     401              :  * @todo Remove below lines after this issue is addressed.
     402              :  * Tizen issues: After python version has been upgraded from 3.9.1 to 3.9.10,
     403              :  * python converter is stopped at Py_Finalize. Since Py_Initialize is not called
     404              :  * twice from this object, Py_Finalize is temporarily removed.
     405              :  */
     406              : #if 0
     407              :   /** Python should be initialized and finalized only once */
     408              :   if (Py_IsInitialized())
     409              :     Py_Finalize ();
     410              : #endif
     411          431 : }
     412              : #ifdef __cplusplus
     413              : }
     414              : #endif /* __cplusplus */
        

Generated by: LCOV version 2.0-1