Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1-only */
2 : /**
3 : * @file nnstreamer_python3_helper.cc
4 : * @date 10 Apr 2019
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
9 : *
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
12 : *
13 : * -- Example python script
14 : * import numpy as np
15 : * import nnstreamer_python as nns
16 : * dim = nns.TensorShape([1,2,3], np.uint8)
17 : */
18 :
19 : #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
20 : #pragma GCC diagnostic ignored "-Wformat"
21 : #endif
22 :
23 : #include <nnstreamer_util.h>
24 : #include "nnstreamer_python3_helper.h"
25 :
26 : #ifdef __cplusplus
27 : extern "C" {
28 : #endif
29 :
30 : /** @brief object structure for custom Python type: TensorShape */
31 : typedef struct {
32 : PyObject_HEAD PyObject *dims;
33 : PyArray_Descr *type;
34 : } TensorShapeObject;
35 :
36 : /** @brief define a prototype for this python module */
37 : PyMODINIT_FUNC PyInit_nnstreamer_python (void);
38 :
39 : /**
40 : * @brief method impl. for setDims
41 : * @param self : Python type object
42 : * @param args : arguments for the method
43 : */
44 : static PyObject *
45 122 : TensorShape_setDims (TensorShapeObject *self, PyObject *args)
46 : {
47 : PyObject *dims;
48 : PyObject *new_dims;
49 : Py_ssize_t i, len;
50 :
51 : /** PyArg_ParseTuple() returns borrowed references */
52 122 : if (!PyArg_ParseTuple (args, "O", &dims))
53 0 : Py_RETURN_NONE;
54 :
55 122 : len = PyList_Size (dims);
56 122 : if (len < 0 || len > NNS_TENSOR_RANK_LIMIT)
57 0 : Py_RETURN_NONE;
58 :
59 122 : if (len < NNS_TENSOR_RANK_LIMIT) {
60 205 : for (i = 0; i < NNS_TENSOR_RANK_LIMIT - len; i++)
61 190 : PyList_Append (dims, PyLong_FromLong (0));
62 15 : new_dims = dims;
63 15 : Py_XINCREF (new_dims);
64 : } else {
65 : /** PyList_GetSlice() returns new reference */
66 107 : new_dims = PyList_GetSlice (dims, 0, NNS_TENSOR_RANK_LIMIT);
67 : }
68 :
69 : /** swap 'self->dims' */
70 122 : Py_SAFEDECREF (self->dims);
71 122 : self->dims = new_dims;
72 :
73 122 : Py_RETURN_NONE;
74 : }
75 :
76 : /**
77 : * @brief method impl. for getDims
78 : * @param self : Python type object
79 : * @param args : arguments for the method
80 : */
81 : static PyObject *
82 132 : TensorShape_getDims (TensorShapeObject *self, PyObject *args)
83 : {
84 : UNUSED (args);
85 132 : return Py_BuildValue ("O", self->dims);
86 : }
87 :
88 : /**
89 : * @brief method impl. for getType
90 : * @param self : Python type object
91 : * @param args : arguments for the method
92 : */
93 : static PyObject *
94 118 : TensorShape_getType (TensorShapeObject *self, PyObject *args)
95 : {
96 : UNUSED (args);
97 118 : return Py_BuildValue ("O", self->type);
98 : }
99 :
100 : /**
101 : * @brief new callback for custom type object
102 : * @param self : Python type object
103 : * @param args : arguments for the method
104 : * @param kw : keywords for the arguments
105 : */
106 : static PyObject *
107 122 : TensorShape_new (PyTypeObject *type, PyObject *args, PyObject *kw)
108 : {
109 122 : TensorShapeObject *self = (TensorShapeObject *) type->tp_alloc (type, 0);
110 : UNUSED (args);
111 : UNUSED (kw);
112 :
113 122 : g_assert (self);
114 :
115 : /** Assign default values */
116 122 : self->dims = PyList_New (0);
117 122 : self->type = PyArray_DescrFromType (NPY_UINT8);
118 122 : Py_XINCREF (self->type);
119 :
120 122 : return (PyObject *) self;
121 : }
122 :
123 : /**
124 : * @brief init callback for custom type object
125 : * @param self : Python type object
126 : * @param args : arguments for the method
127 : * @param kw : keywords for the arguments
128 : */
129 : static int
130 122 : TensorShape_init (TensorShapeObject *self, PyObject *args, PyObject *kw)
131 : {
132 122 : char *keywords[] = { (char *) "dims", (char *) "type", NULL };
133 122 : PyObject *dims = NULL;
134 122 : PyObject *type = NULL;
135 :
136 122 : if (!PyArg_ParseTupleAndKeywords (args, kw, "|OO", keywords, &dims, &type))
137 0 : return -1;
138 :
139 122 : if (dims) {
140 122 : PyObject *none = PyObject_CallMethod (
141 : (PyObject *) self, (char *) "setDims", (char *) "O", dims);
142 122 : Py_SAFEDECREF (none);
143 : }
144 :
145 122 : if (type) {
146 : PyArray_Descr *dtype;
147 122 : if (PyArray_DescrConverter (type, &dtype) != NPY_FAIL) {
148 : /** swap 'self->type' */
149 122 : Py_SAFEDECREF (self->type);
150 122 : self->type = dtype;
151 122 : Py_XINCREF (self->type);
152 : } else
153 0 : Py_ERRMSG ("Wrong data type.");
154 : }
155 :
156 122 : return 0;
157 : }
158 :
159 : /**
160 : * @brief dealloc callback for custom type object
161 : * @param self : Python type object
162 : */
163 : static void
164 78 : TensorShape_dealloc (TensorShapeObject *self)
165 : {
166 78 : Py_SAFEDECREF (self->dims);
167 78 : Py_SAFEDECREF (self->type);
168 78 : Py_TYPE (self)->tp_free ((PyObject *) self);
169 78 : }
170 :
171 : /** @brief members for custom type object */
172 : static PyMemberDef TensorShape_members[]
173 : = { { (char *) "dims", T_OBJECT_EX, offsetof (TensorShapeObject, dims), 0, NULL },
174 : { (char *) "type", T_OBJECT_EX, offsetof (TensorShapeObject, type), 0, NULL } };
175 :
176 : /** @brief methods for custom type object */
177 : static PyMethodDef TensorShape_methods[] = { { (char *) "setDims", (PyCFunction) TensorShape_setDims,
178 : METH_VARARGS | METH_KEYWORDS, NULL },
179 : { (char *) "getDims", (PyCFunction) TensorShape_getDims, METH_VARARGS | METH_KEYWORDS, NULL },
180 : { (char *) "getType", (PyCFunction) TensorShape_getType, METH_VARARGS | METH_KEYWORDS, NULL },
181 : { NULL, NULL, 0, NULL } };
182 :
183 : /** @brief Structure for custom type object */
184 541 : static PyTypeObject TensorShapeType = [] {
185 : #pragma GCC diagnostic push
186 : #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
187 541 : PyTypeObject ret = { PyVarObject_HEAD_INIT (NULL, 0) };
188 : #pragma GCC diagnostic pop
189 541 : ret.tp_name = "nnstreamer_python.TensorShape";
190 541 : ret.tp_basicsize = sizeof (TensorShapeObject);
191 541 : ret.tp_itemsize = 0;
192 541 : ret.tp_dealloc = (destructor) TensorShape_dealloc;
193 541 : ret.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
194 541 : ret.tp_doc = "TensorShape type";
195 541 : ret.tp_methods = TensorShape_methods;
196 541 : ret.tp_members = TensorShape_members;
197 541 : ret.tp_init = (initproc) TensorShape_init;
198 541 : ret.tp_new = TensorShape_new;
199 541 : return ret;
200 : }();
201 :
202 : #pragma GCC diagnostic push
203 : #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
204 : static PyModuleDef nnstreamer_python_module
205 : = { PyModuleDef_HEAD_INIT, "nnstreamer_python", NULL, -1, NULL };
206 : #pragma GCC diagnostic pop
207 :
208 : /** @brief module initialization (python 3.x) */
209 : PyMODINIT_FUNC
210 21 : PyInit_nnstreamer_python (void)
211 : {
212 21 : PyObject *type_object = (PyObject *) &TensorShapeType;
213 : PyObject *module;
214 :
215 : /** Check TensorShape type */
216 21 : if (PyType_Ready (&TensorShapeType) < 0)
217 0 : return NULL;
218 :
219 21 : module = PyModule_Create (&nnstreamer_python_module);
220 21 : if (module == NULL)
221 0 : return NULL;
222 :
223 : /** For numpy array init. */
224 21 : import_array ();
225 :
226 : Py_INCREF (type_object);
227 21 : PyModule_AddObject (module, "TensorShape", type_object);
228 :
229 21 : return module;
230 : }
231 :
232 : /**
233 : * @brief return the data type of the tensor
234 : * @param npyType : the defined type of Python numpy
235 : * @return the enum of defined _NNS_TYPE
236 : */
237 : tensor_type
238 64 : getTensorType (NPY_TYPES npyType)
239 : {
240 64 : switch (npyType) {
241 3 : case NPY_INT32:
242 3 : return _NNS_INT32;
243 0 : case NPY_UINT32:
244 0 : return _NNS_UINT32;
245 4 : case NPY_INT16:
246 4 : return _NNS_INT16;
247 1 : case NPY_UINT16:
248 1 : return _NNS_UINT16;
249 0 : case NPY_INT8:
250 0 : return _NNS_INT8;
251 56 : case NPY_UINT8:
252 56 : return _NNS_UINT8;
253 0 : case NPY_INT64:
254 0 : return _NNS_INT64;
255 0 : case NPY_UINT64:
256 0 : return _NNS_UINT64;
257 0 : case NPY_FLOAT32:
258 0 : return _NNS_FLOAT32;
259 0 : case NPY_FLOAT64:
260 0 : return _NNS_FLOAT64;
261 0 : default:
262 : /** @todo Support other types */
263 0 : break;
264 : }
265 :
266 0 : return _NNS_END;
267 : }
268 :
269 : /**
270 : * @brief return the data type of the tensor for Python numpy
271 : * @param tType : the defined type of NNStreamer
272 : * @return the enum of defined numpy datatypes
273 : */
274 : NPY_TYPES
275 95 : getNumpyType (tensor_type tType)
276 : {
277 95 : switch (tType) {
278 0 : case _NNS_INT32:
279 0 : return NPY_INT32;
280 0 : case _NNS_UINT32:
281 0 : return NPY_UINT32;
282 2 : case _NNS_INT16:
283 2 : return NPY_INT16;
284 2 : case _NNS_UINT16:
285 2 : return NPY_UINT16;
286 0 : case _NNS_INT8:
287 0 : return NPY_INT8;
288 91 : case _NNS_UINT8:
289 91 : return NPY_UINT8;
290 0 : case _NNS_INT64:
291 0 : return NPY_INT64;
292 0 : case _NNS_UINT64:
293 0 : return NPY_UINT64;
294 0 : case _NNS_FLOAT32:
295 0 : return NPY_FLOAT32;
296 0 : case _NNS_FLOAT64:
297 0 : return NPY_FLOAT64;
298 0 : default:
299 : /** @todo Support other types */
300 0 : break;
301 : }
302 0 : return NPY_NOTYPE;
303 : }
304 :
305 : /**
306 : * @brief load the python script
307 : */
308 : int
309 30 : loadScript (PyObject **core_obj, const gchar *module_name, const gchar *class_name)
310 : {
311 30 : PyObject *module = PyImport_ImportModule (module_name);
312 :
313 30 : if (module) {
314 24 : PyObject *cls = PyObject_GetAttrString (module, class_name);
315 24 : Py_SAFEDECREF (module);
316 :
317 24 : if (cls) {
318 23 : *core_obj = PyObject_CallObject (cls, NULL);
319 23 : Py_SAFEDECREF (cls);
320 : } else {
321 1 : Py_ERRMSG ("Cannot find '%s' class in the script.\n", class_name);
322 1 : return -2;
323 : }
324 : } else {
325 6 : Py_ERRMSG ("The script (%s) is not properly loaded.\n", module_name);
326 6 : return -1;
327 : }
328 :
329 23 : return 0;
330 : }
331 :
332 : /**
333 : * @brief loads the dynamic shared object of the python
334 : */
335 : int
336 43 : openPythonLib (void **handle)
337 : {
338 : /**
339 : * To fix import error of python extension modules
340 : * (e.g., multiarray.x86_64-linux-gnu.so: undefined symbol: PyExc_SystemError)
341 : */
342 43 : gchar libname[32] = {
343 : 0,
344 : };
345 :
346 43 : g_snprintf (libname, sizeof (libname), "libpython%d.%d.%s", PY_MAJOR_VERSION,
347 : PY_MINOR_VERSION, SO_EXT);
348 43 : *handle = dlopen (libname, RTLD_LAZY | RTLD_GLOBAL);
349 43 : if (NULL == *handle) {
350 : /* check the python was compiled with '--with-pymalloc' */
351 0 : g_snprintf (libname, sizeof (libname), "libpython%d.%dm.%s",
352 : PY_MAJOR_VERSION, PY_MINOR_VERSION, SO_EXT);
353 :
354 0 : *handle = dlopen (libname, RTLD_LAZY | RTLD_GLOBAL);
355 0 : if (NULL == *handle)
356 0 : return -1;
357 : }
358 :
359 43 : return 0;
360 : }
361 :
362 : /**
363 : * @brief Add custom python module to system path
364 : */
365 : int
366 43 : addToSysPath (const gchar *path)
367 : {
368 : /** Add current/directory path to sys.path */
369 43 : PyObject *sys_module = PyImport_ImportModule ("sys");
370 43 : if (nullptr == sys_module) {
371 0 : Py_ERRMSG ("Cannot import python module 'sys'.");
372 0 : return -1;
373 : }
374 :
375 43 : PyObject *sys_path = PyObject_GetAttrString (sys_module, "path");
376 43 : if (nullptr == sys_path) {
377 0 : Py_ERRMSG ("Cannot import python module 'path'.");
378 0 : Py_SAFEDECREF (sys_module);
379 0 : return -1;
380 : }
381 :
382 43 : PyList_Append (sys_path, PyUnicode_FromString ("."));
383 43 : PyList_Append (sys_path, PyUnicode_FromString (path));
384 :
385 43 : Py_SAFEDECREF (sys_path);
386 43 : Py_SAFEDECREF (sys_module);
387 :
388 43 : return 0;
389 : }
390 :
391 : /**
392 : * @brief parse the converting result to feed output tensors
393 : * @param[result] Python object returned by convert
394 : * @param[info] info Structure for output tensors info
395 : * @return 0 if no error, otherwise negative errno
396 : */
397 : int
398 64 : parseTensorsInfo (PyObject *result, GstTensorsInfo *info)
399 : {
400 64 : Py_ssize_t i, j, num = PyList_Size (result);
401 :
402 64 : if (num < 0 || num > NNS_TENSOR_SIZE_LIMIT)
403 22 : return -1;
404 :
405 42 : gst_tensors_info_init (info);
406 42 : info->num_tensors = (unsigned int) num;
407 106 : for (i = 0; i < num; i++) {
408 64 : GstTensorInfo *_info = gst_tensors_info_get_nth_info (info, (guint) i);
409 :
410 : /** don't own the reference */
411 64 : PyObject *tensor_shape = PyList_GetItem (result, (Py_ssize_t) i);
412 64 : if (nullptr == tensor_shape) {
413 0 : Py_ERRMSG ("The function %s has failed, cannot get TensorShape object.", __FUNCTION__);
414 0 : return -1;
415 : }
416 :
417 64 : PyObject *shape_type = PyObject_CallMethod (tensor_shape, (char *) "getType", NULL);
418 64 : if (nullptr == shape_type) {
419 0 : Py_ERRMSG ("The function %s has failed, cannot get the tensor type.", __FUNCTION__);
420 0 : return -1;
421 : }
422 :
423 : /** convert numpy type to tensor type */
424 64 : _info->type = getTensorType ((NPY_TYPES) (((PyArray_Descr *) shape_type)->type_num));
425 64 : Py_SAFEDECREF (shape_type);
426 :
427 64 : PyObject *shape_dims = PyObject_CallMethod (tensor_shape, (char *) "getDims", NULL);
428 64 : if (nullptr == shape_dims) {
429 0 : Py_ERRMSG ("The function %s has failed, cannot get the tensor dimension.", __FUNCTION__);
430 0 : return -1;
431 : }
432 :
433 64 : if (!PyList_CheckExact (shape_dims)) {
434 0 : Py_ERRMSG ("The function %s has failed, dimension should be a list.", __FUNCTION__);
435 0 : Py_SAFEDECREF (shape_dims);
436 0 : return -EINVAL;
437 : }
438 :
439 64 : Py_ssize_t rank = PyList_Size (shape_dims);
440 64 : if (rank < 0 || rank > NNS_TENSOR_RANK_LIMIT) {
441 0 : Py_ERRMSG ("The function %s has failed, max rank of tensor dimension is %d.",
442 : __FUNCTION__, NNS_TENSOR_RANK_LIMIT);
443 0 : Py_SAFEDECREF (shape_dims);
444 0 : return -EINVAL;
445 : }
446 :
447 1088 : for (j = 0; j < rank; j++) {
448 1024 : PyErr_Clear ();
449 1024 : PyObject *item = PyList_GetItem (shape_dims, (Py_ssize_t) j);
450 1024 : int val = -1;
451 :
452 1024 : if (PyErr_Occurred ()) {
453 0 : PyErr_Print ();
454 0 : PyErr_Clear ();
455 0 : _info->dimension[j] = 0;
456 0 : Py_ERRMSG ("Python nnstreamer plugin has returned dimensions of the %zd'th tensor not in an array. Python code should return int-type array for dimensions. Indexes are counted from 0.\n",
457 : i + 1);
458 0 : Py_SAFEDECREF (shape_dims);
459 0 : return -EINVAL;
460 : }
461 :
462 1024 : if (PyLong_Check (item)) {
463 1021 : val = (int) PyLong_AsLong (item);
464 3 : } else if (PyFloat_Check (item)) {
465 : /** Regard this as a warning. Don't return -EINVAL with this */
466 3 : val = (int) PyFloat_AsDouble (item);
467 3 : Py_ERRMSG ("Python nnstreamer plugin has returned the %zd'th dimension value of the %zd'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",
468 : j + 1, i + 1, PyFloat_AsDouble (item));
469 : } else {
470 0 : _info->dimension[j] = 0;
471 0 : Py_ERRMSG ("Python nnstreamer plugin has returned the %zd'th dimension value of the %zd'th tensor neither in integer or floating-pointer. Python code should return int-type for dimension values. Indexes are counted from 0.\n",
472 : j + 1, i + 1);
473 0 : Py_SAFEDECREF (shape_dims);
474 0 : return -EINVAL;
475 : }
476 :
477 1024 : if (val < 0) {
478 0 : Py_ERRMSG ("The %zd'th dimension value of the %zd'th tensor is invalid (%d).",
479 : j + 1, i + 1, val);
480 0 : Py_SAFEDECREF (shape_dims);
481 0 : return -EINVAL;
482 : }
483 :
484 1024 : _info->dimension[j] = (uint32_t) val;
485 : }
486 :
487 64 : _info->name = NULL;
488 64 : Py_SAFEDECREF (shape_dims);
489 : }
490 :
491 : /* Validate output tensors info after parsing python object. */
492 42 : if (!gst_tensors_info_validate (info)) {
493 0 : gchar *info_str = gst_tensors_info_to_string (info);
494 0 : Py_ERRMSG ("Failed to parse the tensors information from python script, it may include invalid tensor type or dimension value (%s).",
495 : info_str);
496 0 : g_free (info_str);
497 0 : return -EINVAL;
498 : }
499 :
500 42 : return 0;
501 : }
502 :
503 : /**
504 : * @brief allocate TensorShape object
505 : * @param info : the tensor info
506 : * @return created object
507 : */
508 : PyObject *
509 52 : PyTensorShape_New (PyObject *shape_cls, const GstTensorInfo *info)
510 : {
511 52 : _import_array (); /** for numpy */
512 :
513 52 : PyObject *args = PyTuple_New (2);
514 52 : PyObject *dims = PyList_New (NNS_TENSOR_RANK_LIMIT);
515 52 : PyObject *type = (PyObject *) PyArray_DescrFromType (getNumpyType (info->type));
516 :
517 52 : if (nullptr == args || nullptr == dims || nullptr == type) {
518 0 : Py_ERRMSG ("The function %s has failed, cannot create args.", __FUNCTION__);
519 0 : PyErr_Clear ();
520 0 : Py_SAFEDECREF (args);
521 0 : Py_SAFEDECREF (dims);
522 0 : Py_SAFEDECREF (type);
523 0 : return nullptr;
524 : }
525 :
526 884 : for (int i = 0; i < NNS_TENSOR_RANK_LIMIT; i++)
527 832 : PyList_SetItem (dims, i, PyLong_FromLong ((uint64_t) info->dimension[i]));
528 :
529 52 : PyTuple_SetItem (args, 0, dims);
530 52 : PyTuple_SetItem (args, 1, type);
531 :
532 52 : return PyObject_CallObject (shape_cls, args);
533 : /* Its value is checked by setInputTensorDim */
534 : }
535 :
536 : static int python_init_counter = 0;
537 : static PyThreadState *st = NULL;
538 : /**
539 : * @brief Py_Initialize common wrapper for Python subplugins
540 : * @note This prevents a python-using subplugin finalizing another subplugin's
541 : * python interpreter by sharing the reference counter.
542 : */
543 : void
544 486 : nnstreamer_python_init_refcnt ()
545 : {
546 486 : if (!Py_IsInitialized ()) {
547 474 : Py_Initialize ();
548 : PyEval_InitThreads_IfGood ();
549 474 : st = PyEval_SaveThread ();
550 : }
551 486 : python_init_counter++;
552 486 : }
553 :
554 : /**
555 : * @brief Py_Finalize common wrapper for Python subplugins
556 : * @note This prevents a python-using subplugin finalizing another subplugin's
557 : * python interpreter by sharing the reference counter.
558 : */
559 : void
560 486 : nnstreamer_python_fini_refcnt ()
561 : {
562 486 : python_init_counter--;
563 486 : if (python_init_counter == 0) {
564 : /**
565 : * @todo Python Finalize() is buggy and leaky.
566 : * Do not call it until Python is fixed. (not fixed as in 2023-12)
567 : * Calling Finalize at this state will leave modules randomly, which
568 : * may cause assertion failure at exit.
569 : PyEval_RestoreThread (st);
570 : Py_Finalize ();
571 : */
572 : }
573 486 : }
574 :
575 : /**
576 : * @brief Check Py_Init status for python eval functions.
577 : * @return 0 if it's ready. negative error value if it's not ready.
578 : */
579 : int
580 486 : nnstreamer_python_status_check ()
581 : {
582 486 : if (python_init_counter == 0) {
583 0 : fprintf (stderr, "nnstreamer_python_init_refcnt() is not called or it's already closed.");
584 0 : return -EINVAL;
585 : }
586 :
587 486 : if (!Py_IsInitialized ()) {
588 0 : fprintf (stderr, "Py_IsInitialized () is FALSE. If nnstreamer is called by python context, please ignore this error.");
589 0 : return -EINVAL;
590 : }
591 486 : return 0;
592 : }
593 :
594 : #ifdef __cplusplus
595 : } /* extern "C" */
596 : #endif
|