diff --git a/embed/README.md b/embed/README.md index 7874ca9..db643b4 100644 --- a/embed/README.md +++ b/embed/README.md @@ -51,3 +51,22 @@ python3 embed_dec.py loss: 0.145 ``` +# Test 3 - radae_tx as C program + +First pass at the a C callable version of `radae_tx`. Have hard coded a few arguments for convenience, and it's a C application (rather than a library). If this was in library form we would be ready for linking with other C applications. + +1. Generate some test data, and run `embed/radae_tx.py` with Python top level to test Python code. + ``` + cd radae/build + cmake .. + ctest -V -R radae_tx_embed + ``` + +2. Build and C top level/embedded Python version: + ``` + gcc radae_tx.c -o radae_tx $(python3.10-config --cflags) $(python3.10-config --ldflags --embed) + cat ../features_in.f32 | PYTHONPATH="." ./radae_tx radae_tx > tx.f32 + diff tx.f32 ../rx.f32 + ``` + `diff` shows Python ctest and C/Embedded version have same output. Haven't made this a ctest yet as not sure how to do `gcc` step in cmake such that's it's reasonably portable. + diff --git a/embed/radae_tx.c b/embed/radae_tx.c index 4cdacce..4c928fa 100644 --- a/embed/radae_tx.c +++ b/embed/radae_tx.c @@ -4,15 +4,47 @@ #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #include "numpy/arrayobject.h" -#define NARGS 2 +/* help function to call a Python "getter" function with no arguments that returns a long */ +long call_getter(PyObject *pModule, char func_name[]) { + PyObject *pFunc, *pValue; + pFunc = PyObject_GetAttrString(pModule, func_name); + long ret; + + if (pFunc && PyCallable_Check(pFunc)) { + pValue = PyObject_CallObject(pFunc, NULL); + if (pValue != NULL) { + ret = PyLong_AsLong(pValue); + Py_DECREF(pValue); + } + else { + Py_DECREF(pFunc); + PyErr_Print(); + fprintf(stderr,"Call to %s failed\n", func_name); + // TODO when porting to library modify function to return error code, caller shuts down gracefully + exit(1); + } + } + else { + if (PyErr_Occurred()) + PyErr_Print(); + fprintf(stderr, "Cannot find function \"%s\"\n", func_name); + // TODO: fix when ported to library + exit(1); + } + Py_XDECREF(pFunc); + + return ret; +} int main(void) { PyObject *pName, *pModule, *pFunc; PyObject *pValue; - //PyObject *pArgs; + PyObject *pArgs; char *python_name = "radae_tx"; - char *func_name = "get_nb_floats"; + char *do_radae_tx_func_name = "do_radae_tx"; + char *do_eoo_func_name = "do_eoo"; + long nb_floats, Nmf, Neoo; Py_Initialize(); @@ -27,63 +59,78 @@ int main(void) Py_DECREF(pName); if (pModule != NULL) { - pFunc = PyObject_GetAttrString(pModule, func_name); - /* pFunc is a new reference */ - + nb_floats = call_getter(pModule, "get_nb_floats"); + Nmf = call_getter(pModule, "get_Nmf"); + Neoo = call_getter(pModule, "get_Neoo"); + fprintf(stderr, "nb_floats: %ld Nmf: %ld Neoo: %ld\n", nb_floats, Nmf, Neoo); + + pFunc = PyObject_GetAttrString(pModule, do_radae_tx_func_name); if (pFunc && PyCallable_Check(pFunc)) { -#ifdef T - pArgs = PyTuple_New(NARGS); - // first two args from command line - pValue = PyLong_FromLong(atol(argv[3])); - PyTuple_SetItem(pArgs, 0, pValue); - pValue = PyLong_FromLong(atol(argv[4])); - PyTuple_SetItem(pArgs, 1, pValue); + pArgs = PyTuple_New(2); - // 3rd Python function arg - set up numpy array - long dims = 3; - float arr_in[] = {1.0,2.0,3.0}; - pValue = PyArray_SimpleNewFromData(1, &dims, NPY_FLOAT, arr_in); + // 1st Python function arg - numpy array of float features + float buffer_f32[nb_floats]; + pValue = PyArray_SimpleNewFromData(1, &nb_floats, NPY_FLOAT, buffer_f32); if (pValue == NULL) { PyErr_Print(); - fprintf(stderr,"Error setting up numpy array\n"); + fprintf(stderr,"Error setting up numpy array for buffer_f32\n"); } - PyTuple_SetItem(pArgs, 2, pValue); + PyTuple_SetItem(pArgs, 0, pValue); - // 4th Python arg is a numpy array used for output to C - float arr_out[] = {0.0,0.0,0.0}; - pValue = PyArray_SimpleNewFromData(1, &dims, NPY_FLOAT, arr_out); + // 2nd Python arg is a numpy array used for output to C + float tx_out[2*Nmf]; + pValue = PyArray_SimpleNewFromData(1, &Nmf, NPY_CFLOAT, tx_out); if (pValue == NULL) { PyErr_Print(); - fprintf(stderr,"Error setting up numpy array\n"); + fprintf(stderr,"Error setting up numpy array for tx_out\n"); } - PyTuple_SetItem(pArgs, 3, pValue); -#endif - - // do the function call - pValue = PyObject_CallObject(pFunc, NULL); - //Py_DECREF(pArgs); - if (pValue != NULL) { - printf("Result of call: %ld\n", PyLong_AsLong(pValue)); - Py_DECREF(pValue); - - // not sure how to return arrays but can modify input arrays in place as a hack - //printf("returned array: %f %f %f\n", arr_out[0], arr_out[1], arr_out[2]); + PyTuple_SetItem(pArgs, 1, pValue); + + // We are assuming once args are set up we can make repeat call with the same args, even though + // data in arrays changes + while((unsigned)nb_floats == fread(buffer_f32, sizeof(float), nb_floats, stdin)) { + // do the function call + PyObject_CallObject(pFunc, pArgs); + fwrite(tx_out, 2*sizeof(float), Nmf, stdout); + fflush(stdout); } - else { - Py_DECREF(pFunc); - Py_DECREF(pModule); + + Py_DECREF(pArgs); + } + else { + if (PyErr_Occurred()) + PyErr_Print(); + fprintf(stderr, "Cannot find function \"%s\"\n", do_radae_tx_func_name); + } + Py_XDECREF(pFunc); + + // End of Over + pFunc = PyObject_GetAttrString(pModule, do_eoo_func_name); + if (pFunc && PyCallable_Check(pFunc)) { + + pArgs = PyTuple_New(1); + + // Python arg is a numpy array used for output to C + float eoo_out[2*Neoo]; + pValue = PyArray_SimpleNewFromData(1, &Neoo, NPY_CFLOAT, eoo_out); + if (pValue == NULL) { PyErr_Print(); - fprintf(stderr,"Call failed\n"); - return 1; + fprintf(stderr,"Error setting up numpy array for eoo_out\n"); } + PyTuple_SetItem(pArgs, 0, pValue); + PyObject_CallObject(pFunc, pArgs); + fwrite(eoo_out, 2*sizeof(float), Neoo, stdout); + fflush(stdout); + Py_DECREF(pArgs); } else { if (PyErr_Occurred()) PyErr_Print(); - fprintf(stderr, "Cannot find function \"%s\"\n", func_name); + fprintf(stderr, "Cannot find function \"%s\"\n", do_eoo_func_name); } Py_XDECREF(pFunc); + Py_DECREF(pModule); } else { diff --git a/embed/radae_tx.py b/embed/radae_tx.py index 82793b3..428ca40 100644 --- a/embed/radae_tx.py +++ b/embed/radae_tx.py @@ -71,11 +71,15 @@ nb_floats = model.Nzmf*model.enc_stride*nb_total_features # number of output csingles per processing frame Nmf = int((model.Ns+1)*(model.M+model.Ncp)) +# number of output csingles for EOO frame +Neoo = int((model.Ns+2)*(model.M+model.Ncp)) def get_nb_floats(): return nb_floats def get_Nmf(): return Nmf +def get_Neoo(): + return Neoo def do_radae_tx(buffer_f32,tx_out): @@ -96,6 +100,12 @@ def do_radae_tx(buffer_f32,tx_out): # not very Pythonic but works (TODO work out how to return numpy vecs to C) np.copyto(tx_out,tx) +# send end of over frame +def do_eoo(tx_out): + eoo = model.eoo + eoo = eoo.cpu().detach().numpy().flatten().astype('csingle') + np.copyto(tx_out,eoo) + if __name__ == '__main__': tx_out = np.zeros(Nmf,dtype=np.csingle) while True: @@ -104,10 +114,8 @@ def do_radae_tx(buffer_f32,tx_out): break buffer_f32 = np.frombuffer(buffer,np.single) do_radae_tx(buffer_f32,tx_out) - if use_stdout: - sys.stdout.buffer.write(tx_out) + sys.stdout.buffer.write(tx_out) - if use_stdout: - eoo = model.eoo - eoo = eoo.cpu().detach().numpy().flatten().astype('csingle') - sys.stdout.buffer.write(eoo) + eoo_out = np.zeros(Neoo,dtype=np.csingle) + do_eoo(eoo_out) + sys.stdout.buffer.write(eoo_out)