filter::python3: do not break converter/decoder
[platform/upstream/nnstreamer.git] / ext / nnstreamer / tensor_filter / tensor_filter_python3.cc
1 /**
2  * GStreamer Tensor_Filter, Python Module
3  * Copyright (C) 2019 Samsung Electronics Co., Ltd. All rights reserved.
4  * Copyright (C) 2019 Dongju Chae <dongju.chae@samsung.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation;
9  * version 2.1 of the License.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  */
17 /**
18  * @file    tensor_filter_python3.cc
19  * @date    10 Apr 2019
20  * @brief   Python3 module for tensor_filter gstreamer plugin
21  * @see     http://github.com/nnstreamer/nnstreamer
22  * @author  Dongju Chae <dongju.chae@samsung.com>
23  * @bug     No known bugs except for NYI items
24  * @todo    This would be better if this inherits tensor_filter_subplugin class.
25  */
26
27 /**
28  * SECTION:element-tensor_filter_python3
29  *
30  * A filter that loads and executes a python3 script implementing a custom
31  * filter.
32  * The python3 script should be provided.
33  *
34  * <refsect2>
35  * <title>Example launch line</title>
36  * |[
37  * gst-launch-1.0 videotestsrc ! video/x-raw,format=RGB,width=640,height=480 !
38  *    tensor_converter ! tensor_filter framework="python3"
39  *    model="${PATH_TO_SCRIPT}" ! tensor_sink
40  * ]|
41  * </refsect2>
42  */
43
44 #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
45 #pragma GCC diagnostic ignored "-Wformat"
46 #endif
47
48 #define NO_ANONYMOUS_NESTED_STRUCT
49 #include <nnstreamer_plugin_api_filter.h>
50 #undef NO_ANONYMOUS_NESTED_STRUCT
51 #include <map>
52 #include <nnstreamer_conf.h>
53 #include <nnstreamer_util.h>
54 #include "nnstreamer_python3_helper.h"
55
56 /**
57  * @brief Macro for debug mode.
58  */
59 #ifndef DBG
60 #define DBG FALSE
61 #endif
62
63 static const gchar *python_accl_support[] = { NULL };
64
65 /** @brief Callback type for custom filter */
66 typedef enum _cb_type {
67   CB_SETDIM = 0,
68   CB_GETDIM,
69
70   CB_END,
71 } cb_type;
72
73 #define Py_LOCK() PyGILState_Ensure ()
74 #define Py_UNLOCK(gstate) PyGILState_Release (gstate)
75
76 /**
77  * @brief       Python embedding core structure
78  */
79 class PYCore
80 {
81   public:
82   /**
83    * member functions.
84    */
85   PYCore (const char *_script_path, const char *_custom);
86   ~PYCore ();
87
88   int init (const GstTensorFilterProperties *prop);
89   int loadScript ();
90   const char *getScriptPath ();
91   int getInputTensorDim (GstTensorsInfo *info);
92   int getOutputTensorDim (GstTensorsInfo *info);
93   int setInputTensorDim (const GstTensorsInfo *in_info, GstTensorsInfo *out_info);
94   int run (const GstTensorMemory *input, GstTensorMemory *output);
95
96   void freeOutputTensors (void *data);
97
98   /** @brief Return callback type */
99   cb_type getCbType ()
100   {
101     return callback_type;
102   }
103
104   int checkTensorType (int nns_type, int np_type);
105   int checkTensorSize (GstTensorMemory *output, PyArrayObject *array);
106
107   private:
108   const std::string script_path; /**< from model_path property */
109   const std::string module_args; /**< from custom property */
110
111   std::string module_name;
112   std::map<void *, PyArrayObject *> outputArrayMap;
113
114   cb_type callback_type;
115
116   PyObject *core_obj;
117   PyObject *shape_cls;
118
119   GstTensorsInfo inputTensorMeta; /**< The tensor info of input tensors */
120   GstTensorsInfo outputTensorMeta; /**< The tensor info of output tensors */
121
122   bool configured; /**< True if the script is successfully loaded */
123   void *handle; /**< returned handle by dlopen() */
124 };
125
126 #ifdef __cplusplus
127 extern "C" {
128 #endif /* __cplusplus */
129 void init_filter_py (void) __attribute__ ((constructor));
130 void fini_filter_py (void) __attribute__ ((destructor));
131 #ifdef __cplusplus
132 }
133 #endif /* __cplusplus */
134
135 /**
136  * @brief       PYCore creator
137  * @param       _script_path    : the logical path to '{script_name}.py' file
138  * @note        the script of _script_path will be loaded simultaneously
139  * @return      Nothing
140  */
141 PYCore::PYCore (const char *_script_path, const char *_custom)
142     : script_path (_script_path), module_args (_custom != NULL ? _custom : "")
143 {
144   if (openPythonLib (&handle))
145     throw std::runtime_error (dlerror ());
146
147   _import_array (); /** for numpy */
148
149   /**
150    * Parse script path to get module name
151    * The module name should drop its extension (i.e., .py)
152    */
153   module_name = script_path;
154
155   const size_t last_idx = module_name.find_last_of ("/\\");
156   if (last_idx != std::string::npos)
157     module_name.erase (0, last_idx + 1);
158
159   const size_t ext_idx = module_name.rfind ('.');
160   if (ext_idx != std::string::npos)
161     module_name.erase (ext_idx);
162
163   addToSysPath (script_path.substr (0, last_idx).c_str ());
164
165   gst_tensors_info_init (&inputTensorMeta);
166   gst_tensors_info_init (&outputTensorMeta);
167
168   callback_type = CB_END;
169   core_obj = NULL;
170   configured = false;
171   shape_cls = NULL;
172 }
173
174 /**
175  * @brief       PYCore Destructor
176  * @return      Nothing
177  */
178 PYCore::~PYCore ()
179 {
180   gst_tensors_info_free (&inputTensorMeta);
181   gst_tensors_info_free (&outputTensorMeta);
182
183   PyGILState_STATE gstate = Py_LOCK ();
184   Py_SAFEDECREF (core_obj);
185   Py_SAFEDECREF (shape_cls);
186
187   PyErr_Clear ();
188   Py_UNLOCK (gstate);
189
190   dlclose (handle);
191 }
192
193 /**
194  * @brief       initialize the object with python script
195  * @return 0 if OK. non-zero if error.
196  */
197 int
198 PYCore::init (const GstTensorFilterProperties *prop)
199 {
200   PyGILState_STATE gstate = Py_LOCK ();
201   int ret = -EINVAL;
202   /** Find nnstreamer_api module */
203   PyObject *api_module = PyImport_ImportModule ("nnstreamer_python");
204   if (api_module == NULL) {
205     Py_ERRMSG ("Cannt find `nnstreamer_python` module");
206     goto exit;
207   }
208
209   shape_cls = PyObject_GetAttrString (api_module, "TensorShape");
210   Py_SAFEDECREF (api_module);
211
212   if (shape_cls == NULL) {
213     Py_ERRMSG ("Failed to get `TensorShape` from `nnstreamer_python` module");
214     goto exit;
215   }
216
217   gst_tensors_info_copy (&inputTensorMeta, &prop->input_meta);
218   gst_tensors_info_copy (&outputTensorMeta, &prop->output_meta);
219
220   ret = loadScript ();
221 exit:
222   Py_UNLOCK (gstate);
223   return ret;
224 }
225
226 /**
227  * @brief       get the script path
228  * @return the script path.
229  */
230 const char *
231 PYCore::getScriptPath ()
232 {
233   return script_path.c_str ();
234 }
235
236 /**
237  * @brief       load the py script
238  * @note        the script will be loaded
239  * @return 0 if OK. non-zero if error.
240  *        -1 if the py file is not loaded.
241  *        -2 if the target class is not found.
242  *        -3 if the class is not created.
243  */
244 int
245 PYCore::loadScript ()
246 {
247 #if (DBG)
248   gint64 start_time = g_get_real_time ();
249 #endif
250
251   int ret = -EINVAL;
252   PyGILState_STATE gstate = Py_LOCK ();
253
254   PyObject *module = PyImport_ImportModule (module_name.c_str ());
255   if (module) {
256     PyObject *cls = PyObject_GetAttrString (module, "CustomFilter");
257     if (cls) {
258       PyObject *py_args;
259       if (!module_args.empty ()) {
260         gchar **g_args = g_strsplit (module_args.c_str (), " ", 0);
261         char **args = g_args;
262         int argc = 0;
263         while (*(args++) != NULL)
264           argc++;
265
266         if (argc < 1) {
267           g_strfreev (g_args);
268           ml_loge ("Cannot load python script for python-custom-filter.\n");
269           goto exit;
270         }
271
272         py_args = PyTuple_New (argc);
273
274         for (int i = 0; i < argc; i++)
275           PyTuple_SetItem (py_args, i, PyUnicode_FromString (g_args[i]));
276
277         core_obj = PyObject_CallObject (cls, py_args);
278
279         Py_SAFEDECREF (py_args);
280         g_strfreev (g_args);
281       } else
282         core_obj = PyObject_CallObject (cls, NULL);
283
284       if (core_obj) {
285         /** check whther either setInputDim or getInputDim/getOutputDim are
286          * defined */
287         if (PyObject_HasAttrString (core_obj, (char *) "setInputDim"))
288           callback_type = CB_SETDIM;
289         else if (PyObject_HasAttrString (core_obj, (char *) "getInputDim")
290                  && PyObject_HasAttrString (core_obj, (char *) "getOutputDim"))
291           callback_type = CB_GETDIM;
292         else
293           callback_type = CB_END;
294       } else {
295         Py_ERRMSG ("Fail to create an instance 'CustomFilter'\n");
296         ret = -3;
297         goto exit;
298       }
299
300       Py_SAFEDECREF (cls);
301     } else {
302       Py_ERRMSG ("Cannot find 'CustomFilter' class in the script\n");
303       ret = -2;
304       goto exit;
305     }
306
307     Py_SAFEDECREF (module);
308   } else {
309     Py_ERRMSG ("the script is not properly loaded\n");
310     ret = -1;
311     goto exit;
312   }
313
314   configured = true;
315
316 #if (DBG)
317   gint64 stop_time = g_get_real_time ();
318   g_message ("Script is loaded: %" G_GINT64_FORMAT, (stop_time - start_time));
319 #endif
320
321   ret = 0;
322 exit:
323   Py_UNLOCK (gstate);
324   return ret;
325 }
326
327 /**
328  * @brief       check the data type of tensors in array
329  * @param nns_type : tensor type for output tensor
330  * @param np_type  : python array type for output tensor
331  * @return a boolean value for whether the types are matched
332  */
333 int
334 PYCore::checkTensorType (int nns_type, int np_type)
335 {
336   switch (nns_type) {
337     case _NNS_INT64:
338       return np_type == NPY_INT64;
339     case _NNS_UINT64:
340       return np_type == NPY_UINT64;
341     case _NNS_INT32:
342       return np_type == NPY_INT32;
343     case _NNS_UINT32:
344       return np_type == NPY_UINT32;
345     case _NNS_INT16:
346       return np_type == NPY_INT16;
347     case _NNS_UINT16:
348       return np_type == NPY_UINT16;
349     case _NNS_INT8:
350       return np_type == NPY_INT8;
351     case _NNS_UINT8:
352       return np_type == NPY_UINT8;
353     case _NNS_FLOAT64:
354       return np_type == NPY_FLOAT64;
355     case _NNS_FLOAT32:
356       return np_type == NPY_FLOAT32;
357   }
358
359   return 0;
360 }
361
362 /**
363  * @brief       check the data size of tensors in array
364  * @param output : tensor memory for output tensors
365  * @param array  : python array
366  * @return a boolean value for whether the sizes are matched
367  */
368 int
369 PYCore::checkTensorSize (GstTensorMemory *output, PyArrayObject *array)
370 {
371   if (nullptr == output || nullptr == array)
372     throw std::invalid_argument ("Null pointers are given to PYCore::checkTensorSize().\n");
373
374   PyGILState_STATE gstate = Py_LOCK ();
375   size_t total_size = PyArray_ITEMSIZE (array);
376
377   for (int i = 0; i < PyArray_NDIM (array); i++)
378     total_size *= PyArray_DIM (array, i);
379
380   Py_UNLOCK (gstate);
381
382   return (output->size == total_size);
383 }
384
385 /**
386  * @brief       return the Dimension of Input Tensor.
387  * @param[out] info Structure for tensor info.
388  * @todo return whole array rather than index 0
389  * @return 0 if OK. non-zero if error.
390  */
391 int
392 PYCore::getInputTensorDim (GstTensorsInfo *info)
393 {
394   int res = 0;
395
396   if (nullptr == info)
397     throw std::invalid_argument ("A null pointer is given to PYCore::getInputTensorDim().\n");
398
399   PyGILState_STATE gstate = Py_LOCK ();
400
401   PyObject *result = PyObject_CallMethod (core_obj, (char *) "getInputDim", NULL);
402   if (result) {
403     res = parseTensorsInfo (result, info);
404     Py_SAFEDECREF (result);
405   } else {
406     Py_ERRMSG ("Fail to call 'getInputDim'");
407     res = -1;
408   }
409
410   Py_UNLOCK (gstate);
411
412   return res;
413 }
414
415 /**
416  * @brief       return the Dimension of Tensor.
417  * @param[out] info Structure for tensor info.
418  * @todo return whole array rather than index 0
419  * @return 0 if OK. non-zero if error.
420  */
421 int
422 PYCore::getOutputTensorDim (GstTensorsInfo *info)
423 {
424   int res = 0;
425
426   if (nullptr == info)
427     throw std::invalid_argument ("A null pointer is given to PYCore::getOutputTensorDim().\n");
428
429   PyGILState_STATE gstate = Py_LOCK ();
430
431   PyObject *result = PyObject_CallMethod (core_obj, (char *) "getOutputDim", NULL);
432   if (result) {
433     res = parseTensorsInfo (result, info);
434     Py_SAFEDECREF (result);
435   } else {
436     Py_ERRMSG ("Fail to call 'getOutputDim'");
437     res = -1;
438   }
439
440   Py_UNLOCK (gstate);
441
442   return res;
443 }
444
445 /**
446  * @brief       set the Dimension of Input Tensor and return the Dimension of Input Tensor.
447  * @param[in] info Structure for input tensor info.
448  * @param[out] info Structure for output tensor info.
449  * @todo return whole array rather than index 0
450  * @return 0 if OK. non-zero if error.
451  */
452 int
453 PYCore::setInputTensorDim (const GstTensorsInfo *in_info, GstTensorsInfo *out_info)
454 {
455   GstTensorInfo *_info;
456   int res = 0;
457
458   if (nullptr == in_info || nullptr == out_info)
459     throw std::invalid_argument ("Null pointers are given to PYCore::setInputTensorDim().\n");
460
461   PyGILState_STATE gstate = Py_LOCK ();
462
463   /** to Python list object */
464   PyObject *param = PyList_New (in_info->num_tensors);
465   if (nullptr == param)
466     throw std::runtime_error ("PyList_New(); has failed.");
467
468   for (unsigned int i = 0; i < in_info->num_tensors; i++) {
469     PyObject *shape;
470
471     _info = gst_tensors_info_get_nth_info ((GstTensorsInfo *) in_info, i);
472     shape = PyTensorShape_New (shape_cls, _info);
473     if (nullptr == shape)
474       throw std::runtime_error ("PyTensorShape_New(); has failed.");
475
476     PyList_SetItem (param, i, shape);
477   }
478
479   PyObject *result
480       = PyObject_CallMethod (core_obj, (char *) "setInputDim", (char *) "(O)", param);
481
482   Py_SAFEDECREF (param);
483
484   if (result) {
485     gst_tensors_info_copy (&inputTensorMeta, in_info);
486     res = parseTensorsInfo (result, out_info);
487     if (res == 0)
488       gst_tensors_info_copy (&outputTensorMeta, out_info);
489     Py_SAFEDECREF (result);
490   } else {
491     Py_ERRMSG ("Fail to call 'setInputDim'");
492     res = -1;
493   }
494
495   Py_UNLOCK (gstate);
496
497   return res;
498 }
499
500 /**
501  * @brief free output tensor corresponding to the given data
502  * @param[data] The data element
503  */
504 void
505 PYCore::freeOutputTensors (void *data)
506 {
507   std::map<void *, PyArrayObject *>::iterator it;
508   PyGILState_STATE gstate = Py_LOCK ();
509
510   it = outputArrayMap.find (data);
511   if (it != outputArrayMap.end ()) {
512     Py_SAFEDECREF (it->second);
513     outputArrayMap.erase (it);
514   } else {
515     ml_loge ("Cannot find output data: 0x%lx", (unsigned long) data);
516   }
517
518   Py_UNLOCK (gstate);
519 }
520
521 /**
522  * @brief       run the script with the input.
523  * @param[in] input : The array of input tensors
524  * @param[out]  output : The array of output tensors
525  * @return 0 if OK. non-zero if error.
526  *        -1 if the script does not work properly.
527  *        -2 if the output properties are different with script.
528  */
529 int
530 PYCore::run (const GstTensorMemory *input, GstTensorMemory *output)
531 {
532   GstTensorInfo *_info;
533   int res = 0;
534   PyObject *result;
535
536 #if (DBG)
537   gint64 start_time = g_get_real_time ();
538 #endif
539
540   if (nullptr == output || nullptr == input)
541     throw std::invalid_argument ("Null pointers are given to PYCore::run().\n");
542
543   PyGILState_STATE gstate = Py_LOCK ();
544
545   PyObject *param = PyList_New (inputTensorMeta.num_tensors);
546   for (unsigned int i = 0; i < inputTensorMeta.num_tensors; i++) {
547     _info = gst_tensors_info_get_nth_info (&inputTensorMeta, i);
548
549     /** create a Numpy array wrapper (1-D) for NNS tensor data */
550     tensor_type nns_type = _info->type;
551     npy_intp input_dims[]
552         = { (npy_intp) (input[i].size / gst_tensor_get_element_size (nns_type)) };
553     PyObject *input_array = PyArray_SimpleNewFromData (
554         1, input_dims, getNumpyType (nns_type), input[i].data);
555     PyList_SetItem (param, i, input_array);
556   }
557
558
559   result = PyObject_CallMethod (core_obj, (char *) "invoke", (char *) "(O)", param);
560
561   if (result) {
562     if ((unsigned int) PyList_Size (result) != outputTensorMeta.num_tensors) {
563       res = -EINVAL;
564       ml_logf ("The Python allocated size mismatched. Cannot proceed.\n");
565       Py_SAFEDECREF (result);
566       goto exit_decref;
567     }
568
569     for (unsigned int i = 0; i < outputTensorMeta.num_tensors; i++) {
570       PyArrayObject *output_array
571           = (PyArrayObject *) PyList_GetItem (result, (Py_ssize_t) i);
572
573       _info = gst_tensors_info_get_nth_info (&outputTensorMeta, i);
574
575       /** type/size checking */
576       if (checkTensorType (_info->type, PyArray_TYPE (output_array))
577           && checkTensorSize (&output[i], output_array)) {
578         /** obtain the pointer to the buffer for the output array */
579         output[i].data = PyArray_DATA (output_array);
580         Py_XINCREF (output_array);
581         outputArrayMap.insert (std::make_pair (output[i].data, output_array));
582       } else {
583         ml_loge ("Output tensor type/size is not matched\n");
584         res = -2;
585         break;
586       }
587     }
588
589     Py_SAFEDECREF (result);
590   } else {
591     Py_ERRMSG ("Fail to call 'invoke'");
592     res = -1;
593   }
594
595 exit_decref:
596   Py_SAFEDECREF (param);
597   Py_UNLOCK (gstate);
598
599 #if (DBG)
600   gint64 stop_time = g_get_real_time ();
601   g_message ("Invoke() is finished: %" G_GINT64_FORMAT, (stop_time - start_time));
602 #endif
603
604   return res;
605 }
606
607 /**
608  * @brief The mandatory callback for GstTensorFilterFramework
609  * @param prop: property of tensor_filter instance
610  * @param private_data : python plugin's private data
611  * @param input : The array of input tensors
612  * @param output : The array of output tensors
613  */
614 static int
615 py_run (const GstTensorFilterProperties *prop, void **private_data,
616     const GstTensorMemory *input, GstTensorMemory *output)
617 {
618   PYCore *core = static_cast<PYCore *> (*private_data);
619   UNUSED (prop);
620
621   g_return_val_if_fail (core, -EINVAL);
622   g_return_val_if_fail (input, -EINVAL);
623   g_return_val_if_fail (output, -EINVAL);
624
625   return core->run (input, output);
626 }
627
628 /**
629  * @brief The optional callback for GstTensorFilterFramework
630  * @param data : The data element.
631  * @param private_data : python plugin's private data
632  */
633 static void
634 py_destroyNotify (void **private_data, void *data)
635 {
636   PYCore *core = static_cast<PYCore *> (*private_data);
637
638   if (core) {
639     core->freeOutputTensors (data);
640   }
641 }
642
643 /**
644  * @brief The optional callback for GstTensorFilterFramework
645  * @param[in] prop read-only property values
646  * @param[in/out] private_data python plugin's private data
647  * @param[in] in_info structure of input tensor info
648  * @param[out] out_info structure of output tensor info
649  */
650 static int
651 py_setInputDim (const GstTensorFilterProperties *prop, void **private_data,
652     const GstTensorsInfo *in_info, GstTensorsInfo *out_info)
653 {
654   PYCore *core = static_cast<PYCore *> (*private_data);
655   UNUSED (prop);
656   g_return_val_if_fail (core && in_info && out_info, -EINVAL);
657
658   if (core->getCbType () != CB_SETDIM) {
659     return -ENOENT;
660   }
661
662   return core->setInputTensorDim (in_info, out_info);
663 }
664
665 /**
666  * @brief The optional callback for GstTensorFilterFramework
667  * @param prop: property of tensor_filter instance
668  * @param private_data : python plugin's private data
669  * @param[out] info The dimesions and types of input tensors
670  */
671 static int
672 py_getInputDim (const GstTensorFilterProperties *prop, void **private_data, GstTensorsInfo *info)
673 {
674   PYCore *core = static_cast<PYCore *> (*private_data);
675   UNUSED (prop);
676   g_return_val_if_fail (core && info, -EINVAL);
677
678   if (core->getCbType () != CB_GETDIM) {
679     return -ENOENT;
680   }
681
682   return core->getInputTensorDim (info);
683 }
684
685 /**
686  * @brief The optional callback for GstTensorFilterFramework
687  * @param prop: property of tensor_filter instance
688  * @param private_data : python plugin's private data
689  * @param[out] info The dimesions and types of output tensors
690  */
691 static int
692 py_getOutputDim (const GstTensorFilterProperties *prop, void **private_data,
693     GstTensorsInfo *info)
694 {
695   PYCore *core = static_cast<PYCore *> (*private_data);
696   UNUSED (prop);
697   g_return_val_if_fail (core && info, -EINVAL);
698
699   if (core->getCbType () != CB_GETDIM) {
700     return -ENOENT;
701   }
702
703   return core->getOutputTensorDim (info);
704 }
705
706 /**
707  * @brief Free privateData and move on.
708  */
709 static void
710 py_close (const GstTensorFilterProperties *prop, void **private_data)
711 {
712   PYCore *core = static_cast<PYCore *> (*private_data);
713   UNUSED (prop);
714
715   g_return_if_fail (core != NULL);
716   delete core;
717
718   *private_data = NULL;
719 }
720
721 /**
722  * @brief Load python model
723  * @param prop: property of tensor_filter instance
724  * @param private_data : python plugin's private data
725  * @return 0 if successfully loaded. 1 if skipped (already loaded).
726  *        -1 if the object construction is failed.
727  *        -2 if the object initialization if failed
728  */
729 static int
730 py_loadScriptFile (const GstTensorFilterProperties *prop, void **private_data)
731 {
732   PYCore *core;
733   const gchar *script_path;
734
735   if (prop->num_models != 1)
736     return -1;
737
738   /**
739    * prop->model_files[0] contains the path of a python script
740    * prop->custom contains its arguments seperated by ' '
741    */
742   core = static_cast<PYCore *> (*private_data);
743   script_path = prop->model_files[0];
744
745   if (core != NULL) {
746     if (g_strcmp0 (script_path, core->getScriptPath ()) == 0)
747       return 1; /* skipped */
748
749     py_close (prop, private_data);
750   }
751
752   /* init null */
753   *private_data = NULL;
754
755   PyGILState_STATE gstate = PyGILState_Ensure ();
756   core = new PYCore (script_path, prop->custom_properties);
757   if (core == NULL) {
758     g_printerr ("Failed to allocate memory for filter subplugin: Python\n");
759     PyGILState_Release (gstate);
760     return -1;
761   }
762
763   if (core->init (prop) != 0) {
764     delete core;
765     g_printerr ("failed to initailize the object: Python\n");
766     PyGILState_Release (gstate);
767     return -2;
768   }
769
770   /** check methods in python script */
771   if (core->getCbType () != CB_SETDIM && core->getCbType () != CB_GETDIM) {
772     delete core;
773     g_printerr ("Wrong callback type\n");
774     PyGILState_Release (gstate);
775     return -2;
776   }
777   PyGILState_Release (gstate);
778
779   *private_data = core;
780
781   return 0;
782 }
783
784 /**
785  * @brief The open callback for GstTensorFilterFramework. Called before anything else
786  * @param prop: property of tensor_filter instance
787  * @param private_data : python plugin's private data
788  */
789 static int
790 py_open (const GstTensorFilterProperties *prop, void **private_data)
791 {
792   if (!Py_IsInitialized ())
793     throw std::runtime_error ("Python is not initialize.");
794   int status = py_loadScriptFile (prop, private_data);
795
796   return status;
797 }
798
799 /**
800  * @brief Check support of the backend
801  * @param hw: backend to check support of
802  */
803 static int
804 py_checkAvailability (accl_hw hw)
805 {
806   if (g_strv_contains (python_accl_support, get_accl_hw_str (hw)))
807     return 0;
808
809   return -ENOENT;
810 }
811
812 static gchar filter_subplugin_python[] = "python3";
813
814 static GstTensorFilterFramework NNS_support_python = { .version = GST_TENSOR_FILTER_FRAMEWORK_V0,
815   .open = py_open,
816   .close = py_close,
817   { .v0 = {
818         .name = filter_subplugin_python,
819         .allow_in_place = FALSE, /** @todo: support this to optimize performance later. */
820         .allocate_in_invoke = TRUE,
821         .run_without_model = FALSE,
822         .verify_model_path = TRUE,
823         .statistics = nullptr,
824         .invoke_NN = py_run,
825         /** dimension-related callbacks are dynamically updated */
826         .getInputDimension = py_getInputDim,
827         .getOutputDimension = py_getOutputDim,
828         .setInputDimension = py_setInputDim,
829         .destroyNotify = py_destroyNotify,
830         .reloadModel = nullptr,
831         .handleEvent = nullptr,
832         .checkAvailability = py_checkAvailability,
833         .allocateInInvoke = nullptr,
834     } } };
835
836 static PyThreadState *st;
837 /** @brief Initialize this object for tensor_filter subplugin runtime register */
838 void
839 init_filter_py (void)
840 {
841   /** Python should be initialized and finalized only once */
842   if (!Py_IsInitialized ())
843     Py_Initialize ();
844   PyEval_InitThreads_IfGood ();
845   st = PyEval_SaveThread ();
846
847   nnstreamer_filter_probe (&NNS_support_python);
848   nnstreamer_filter_set_custom_property_desc (filter_subplugin_python, "${GENERAL_STRING}",
849       "There is no key-value pair defined by python3 subplugin. "
850       "Provide arguments for the given python3 script.",
851       NULL);
852 }
853
854 /** @brief Destruct the subplugin */
855 void
856 fini_filter_py (void)
857 {
858   PyEval_RestoreThread (st);
859   nnstreamer_filter_exit (NNS_support_python.v0.name);
860
861 /**
862  * @todo Remove below lines after this issue is addressed.
863  * Tizen issues: After python version has been upgraded from 3.9.1 to 3.9.10,
864  * python converter is stopped at Py_Finalize. Since Py_Initialize is not called
865  * twice from this object, Py_Finalize is temporarily removed.
866  * We do not know if it's safe to call this at this point.
867  * We can finalize when ALL python subplugins share the same ref counter.
868  */
869 #if 0
870   /** Python should be initialized and finalized only once */
871   if (Py_IsInitialized ())
872     Py_Finalize ();
873 #endif
874 }