1 /* SPDX-License-Identifier: LGPL-2.1-only */
3 * @file nnstreamer_python3_helper.cc
5 * @brief python helper structure for nnstreamer tensor_filter
6 * @see http://github.com/nnstreamer/nnstreamer
7 * @author Dongju Chae <dongju.chae@samsung.com>
8 * @bug No known bugs except for NYI items
10 * A python module that provides a wrapper for internal structures in tensor_filter_python.
11 * Users can import this module to access such a functionality
13 * -- Example python script
15 * import nnstreamer_python as nns
16 * dim = nns.TensorShape([1,2,3], np.uint8)
19 #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
20 #pragma GCC diagnostic ignored "-Wformat"
23 #include <nnstreamer_util.h>
24 #include "nnstreamer_python3_helper.h"
30 /** @brief object structure for custom Python type: TensorShape */
32 PyObject_HEAD PyObject *dims;
36 /** @brief define a prototype for this python module */
37 PyMODINIT_FUNC PyInit_nnstreamer_python (void);
40 * @brief method impl. for setDims
41 * @param self : Python type object
42 * @param args : arguments for the method
45 TensorShape_setDims (TensorShapeObject *self, PyObject *args)
51 /** PyArg_ParseTuple() returns borrowed references */
52 if (!PyArg_ParseTuple (args, "O", &dims))
55 len = PyList_Size (dims);
56 if (len < NNS_TENSOR_RANK_LIMIT) {
57 for (i = 0; i < NNS_TENSOR_RANK_LIMIT - len; i++)
58 PyList_Append (dims, PyLong_FromLong (0));
60 Py_XINCREF (new_dims);
62 /** PyList_GetSlice() returns new reference */
63 new_dims = PyList_GetSlice (dims, 0, NNS_TENSOR_RANK_LIMIT);
66 /** swap 'self->dims' */
67 Py_SAFEDECREF (self->dims);
68 self->dims = new_dims;
74 * @brief method impl. for getDims
75 * @param self : Python type object
76 * @param args : arguments for the method
79 TensorShape_getDims (TensorShapeObject *self, PyObject *args)
82 return Py_BuildValue ("O", self->dims);
86 * @brief method impl. for getType
87 * @param self : Python type object
88 * @param args : arguments for the method
91 TensorShape_getType (TensorShapeObject *self, PyObject *args)
94 return Py_BuildValue ("O", self->type);
98 * @brief new callback for custom type object
99 * @param self : Python type object
100 * @param args : arguments for the method
101 * @param kw : keywords for the arguments
104 TensorShape_new (PyTypeObject *type, PyObject *args, PyObject *kw)
106 TensorShapeObject *self = (TensorShapeObject *) type->tp_alloc (type, 0);
112 /** Assign default values */
113 self->dims = PyList_New (0);
114 self->type = PyArray_DescrFromType (NPY_UINT8);
115 Py_XINCREF (self->type);
117 return (PyObject *) self;
121 * @brief init callback for custom type object
122 * @param self : Python type object
123 * @param args : arguments for the method
124 * @param kw : keywords for the arguments
127 TensorShape_init (TensorShapeObject *self, PyObject *args, PyObject *kw)
129 char *keywords[] = { (char *) "dims", (char *) "type", NULL };
130 PyObject *dims = NULL;
131 PyObject *type = NULL;
133 if (!PyArg_ParseTupleAndKeywords (args, kw, "|OO", keywords, &dims, &type))
137 PyObject *none = PyObject_CallMethod (
138 (PyObject *) self, (char *) "setDims", (char *) "O", dims);
139 Py_SAFEDECREF (none);
143 PyArray_Descr *dtype;
144 if (PyArray_DescrConverter (type, &dtype) != NPY_FAIL) {
145 /** swap 'self->type' */
146 Py_SAFEDECREF (self->type);
148 Py_XINCREF (self->type);
150 Py_ERRMSG ("Wrong data type.");
157 * @brief dealloc callback for custom type object
158 * @param self : Python type object
161 TensorShape_dealloc (TensorShapeObject *self)
163 Py_SAFEDECREF (self->dims);
164 Py_SAFEDECREF (self->type);
165 Py_TYPE (self)->tp_free ((PyObject *) self);
168 /** @brief members for custom type object */
169 static PyMemberDef TensorShape_members[]
170 = { { (char *) "dims", T_OBJECT_EX, offsetof (TensorShapeObject, dims), 0, NULL },
171 { (char *) "type", T_OBJECT_EX, offsetof (TensorShapeObject, type), 0, NULL } };
173 /** @brief methods for custom type object */
174 static PyMethodDef TensorShape_methods[] = { { (char *) "setDims", (PyCFunction) TensorShape_setDims,
175 METH_VARARGS | METH_KEYWORDS, NULL },
176 { (char *) "getDims", (PyCFunction) TensorShape_getDims, METH_VARARGS | METH_KEYWORDS, NULL },
177 { (char *) "getType", (PyCFunction) TensorShape_getType, METH_VARARGS | METH_KEYWORDS, NULL },
178 { NULL, NULL, 0, NULL } };
180 /** @brief Structure for custom type object */
181 static PyTypeObject TensorShapeType = [] {
182 #pragma GCC diagnostic push
183 #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
184 PyTypeObject ret = { PyVarObject_HEAD_INIT (NULL, 0) };
185 #pragma GCC diagnostic pop
186 ret.tp_name = "nnstreamer_python.TensorShape";
187 ret.tp_basicsize = sizeof (TensorShapeObject);
189 ret.tp_dealloc = (destructor) TensorShape_dealloc;
190 ret.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
191 ret.tp_doc = "TensorShape type";
192 ret.tp_methods = TensorShape_methods;
193 ret.tp_members = TensorShape_members;
194 ret.tp_init = (initproc) TensorShape_init;
195 ret.tp_new = TensorShape_new;
199 #pragma GCC diagnostic push
200 #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
201 static PyModuleDef nnstreamer_python_module
202 = { PyModuleDef_HEAD_INIT, "nnstreamer_python", NULL, -1, NULL };
203 #pragma GCC diagnostic pop
205 /** @brief module initialization (python 3.x) */
207 PyInit_nnstreamer_python (void)
209 PyObject *type_object = (PyObject *) &TensorShapeType;
212 /** Check TensorShape type */
213 if (PyType_Ready (&TensorShapeType) < 0)
216 module = PyModule_Create (&nnstreamer_python_module);
220 /** For numpy array init. */
223 Py_INCREF (type_object);
224 PyModule_AddObject (module, "TensorShape", type_object);
230 * @brief return the data type of the tensor
231 * @param npyType : the defined type of Python numpy
232 * @return the enum of defined _NNS_TYPE
235 getTensorType (NPY_TYPES npyType)
259 /** @todo Support other types */
267 * @brief return the data type of the tensor for Python numpy
268 * @param tType : the defined type of NNStreamer
269 * @return the enum of defined numpy datatypes
272 getNumpyType (tensor_type tType)
296 /** @todo Support other types */
303 * @brief load the python script
306 loadScript (PyObject **core_obj, const gchar *module_name, const gchar *class_name)
308 PyObject *module = PyImport_ImportModule (module_name);
311 PyObject *cls = PyObject_GetAttrString (module, class_name);
312 Py_SAFEDECREF (module);
315 *core_obj = PyObject_CallObject (cls, NULL);
318 Py_ERRMSG ("Cannot find '%s' class in the script.\n", class_name);
322 Py_ERRMSG ("The script (%s) is not properly loaded.\n", module_name);
330 * @brief loads the dynamic shared object of the python
333 openPythonLib (void **handle)
336 * To fix import error of python extension modules
337 * (e.g., multiarray.x86_64-linux-gnu.so: undefined symbol: PyExc_SystemError)
339 gchar libname[32] = {
343 g_snprintf (libname, sizeof (libname), "libpython%d.%d.%s", PY_MAJOR_VERSION,
344 PY_MINOR_VERSION, SO_EXT);
345 *handle = dlopen (libname, RTLD_LAZY | RTLD_GLOBAL);
346 if (NULL == *handle) {
347 /* check the python was compiled with '--with-pymalloc' */
348 g_snprintf (libname, sizeof (libname), "libpython%d.%dm.%s",
349 PY_MAJOR_VERSION, PY_MINOR_VERSION, SO_EXT);
351 *handle = dlopen (libname, RTLD_LAZY | RTLD_GLOBAL);
360 * @brief Add custom python module to system path
363 addToSysPath (const gchar *path)
365 /** Add current/directory path to sys.path */
366 PyObject *sys_module = PyImport_ImportModule ("sys");
367 if (nullptr == sys_module) {
368 Py_ERRMSG ("Cannot import python module 'sys'.");
372 PyObject *sys_path = PyObject_GetAttrString (sys_module, "path");
373 if (nullptr == sys_path) {
374 Py_ERRMSG ("Cannot import python module 'path'.");
375 Py_SAFEDECREF (sys_module);
379 PyList_Append (sys_path, PyUnicode_FromString ("."));
380 PyList_Append (sys_path, PyUnicode_FromString (path));
382 Py_SAFEDECREF (sys_path);
383 Py_SAFEDECREF (sys_module);
389 * @brief parse the converting result to feed output tensors
390 * @param[result] Python object retunred by convert
391 * @param[info] info Structure for output tensors info
392 * @return 0 if no error, otherwise negative errno
395 parseTensorsInfo (PyObject *result, GstTensorsInfo *info)
399 if (PyList_Size (result) < 0)
402 gst_tensors_info_init (info);
403 info->num_tensors = PyList_Size (result);
404 for (i = 0; i < info->num_tensors; i++) {
405 /** don't own the reference */
406 PyObject *tensor_shape = PyList_GetItem (result, (Py_ssize_t) i);
407 if (nullptr == tensor_shape) {
408 Py_ERRMSG ("The function %s has failed, cannot get TensorShape object.", __FUNCTION__);
412 PyObject *shape_type = PyObject_CallMethod (tensor_shape, (char *) "getType", NULL);
413 if (nullptr == shape_type) {
414 Py_ERRMSG ("The function %s has failed, cannot get the tensor type.", __FUNCTION__);
418 /** convert numpy type to tensor type */
420 = getTensorType ((NPY_TYPES) (((PyArray_Descr *) shape_type)->type_num));
421 Py_SAFEDECREF (shape_type);
423 PyObject *shape_dims = PyObject_CallMethod (tensor_shape, (char *) "getDims", NULL);
424 if (nullptr == shape_dims) {
425 Py_ERRMSG ("The function %s has failed, cannot get the tensor dimension.", __FUNCTION__);
429 if (!PyList_CheckExact (shape_dims)) {
430 Py_ERRMSG ("The function %s has failed, dimension should be a list.", __FUNCTION__);
431 Py_SAFEDECREF (shape_dims);
435 rank = (guint) PyList_Size (shape_dims);
436 if (rank > NNS_TENSOR_RANK_LIMIT) {
437 Py_ERRMSG ("The function %s has failed, max rank of tensor dimension is %d.",
438 __FUNCTION__, NNS_TENSOR_RANK_LIMIT);
439 Py_SAFEDECREF (shape_dims);
443 for (j = 0; j < rank; j++) {
445 PyObject *item = PyList_GetItem (shape_dims, (Py_ssize_t) j);
448 if (PyErr_Occurred ()) {
451 info->info[i].dimension[j] = 0;
452 Py_ERRMSG ("Python nnstreamer plugin has returned dimensions of the %u'th tensor not in an array. Python code should return int-type array for dimensions. Indexes are counted from 0.\n",
454 Py_SAFEDECREF (shape_dims);
458 if (PyLong_Check (item)) {
459 val = (int) PyLong_AsLong (item);
460 } else if (PyFloat_Check (item)) {
461 /** Regard this as a warning. Don't return -EINVAL with this */
462 val = (int) PyFloat_AsDouble (item);
463 Py_ERRMSG ("Python nnstreamer plugin has returned the %u'th dimension value of the %u'th tensor in floating-point type (%f), which is casted as unsigned-int. Python code should return int-type for dimension values. Indexes are counted from 0.\n",
464 j + 1, i + 1, PyFloat_AsDouble (item));
466 info->info[i].dimension[j] = 0;
467 Py_ERRMSG ("Python nnstreamer plugin has returned the %u'th dimension value of the %u'th tensor neither in integer or floating-pointer. Python code should return int-type for dimension values. Indexes are counted from 0.\n",
469 Py_SAFEDECREF (shape_dims);
474 Py_ERRMSG ("The %u'th dimension value of the %u'th tensor is invalid (%d).",
476 Py_SAFEDECREF (shape_dims);
480 info->info[i].dimension[j] = (uint32_t) val;
483 info->info[i].name = NULL;
484 Py_SAFEDECREF (shape_dims);
487 /* Validate output tensors info after parsing python object. */
488 if (!gst_tensors_info_validate (info)) {
489 gchar *info_str = gst_tensors_info_to_string (info);
490 Py_ERRMSG ("Failed to parse the tensors information from python script, it may include invalid tensor type or dimension value (%s).",
500 * @brief allocate TensorShape object
501 * @param info : the tensor info
502 * @return created object
505 PyTensorShape_New (PyObject *shape_cls, const GstTensorInfo *info)
507 _import_array (); /** for numpy */
509 PyObject *args = PyTuple_New (2);
510 PyObject *dims = PyList_New (NNS_TENSOR_RANK_LIMIT);
511 PyObject *type = (PyObject *) PyArray_DescrFromType (getNumpyType (info->type));
513 if (nullptr == args || nullptr == dims || nullptr == type) {
514 Py_ERRMSG ("The function %s has failed, cannot create args.", __FUNCTION__);
516 Py_SAFEDECREF (args);
517 Py_SAFEDECREF (dims);
518 Py_SAFEDECREF (type);
522 for (int i = 0; i < NNS_TENSOR_RANK_LIMIT; i++)
523 PyList_SetItem (dims, i, PyLong_FromLong ((uint64_t) info->dimension[i]));
525 PyTuple_SetItem (args, 0, dims);
526 PyTuple_SetItem (args, 1, type);
528 return PyObject_CallObject (shape_cls, args);
529 /* Its value is checked by setInputTensorDim */