#include #define DEBUGTRACE_ENABLED #include "arma_npy.h" #include "debugtrace.hpp" #include "lasp_ppm.h" #include "lasp_rtaps.h" #include "lasp_streammgr.h" #include "lasp_threadedindatahandler.h" #include #include #include using namespace std::literals::chrono_literals; using std::cerr; using std::endl; namespace py = pybind11; /** * @brief Generate a Numpy array from daqdata, does *NOT* create a copy of the * data!. Instead, it shares the data from the DaqData container. * * @tparam T The type of the stored sample * @param d The daqdata to convert * * @return Numpy array */ template py::array_t getPyArrayNoCpy(const DaqData &d) { // https://github.com/pybind/pybind11/issues/323 // // When a valid object is passed as 'base', it tells pybind not to take // ownership of the data, because 'base' will own it. In fact 'packet' will // own it, but - psss! - , we don't tell it to pybind... Alos note that ANY // valid object is good for this purpose, so I choose "str"... py::str dummyDataOwner; /* * Signature: array_t(ShapeContainer shape, StridesContainer strides, const T *ptr = nullptr, handle base = handle()); */ return py::array_t( py::array::ShapeContainer({d.nframes, d.nchannels}), // Shape py::array::StridesContainer( // Strides {sizeof(T), sizeof(T) * d.nframes}), // Strides (in bytes) for each index reinterpret_cast( const_cast(d).raw_ptr()), // Pointer to buffer dummyDataOwner // As stated above, now Numpy does not take ownership of // the data pointer. ); } template py::array_t dmat_to_ndarray(const DaqData &d) { // https://github.com/pybind/pybind11/issues/323 // // When a valid object is passed as 'base', it tells pybind not to take // ownership of the data, because 'base' will own it. In fact 'packet' will // own it, but - psss! - , we don't tell it to pybind... Alos note that ANY // valid object is good for this purpose, so I choose "str"... py::str dummyDataOwner; /* * Signature: array_t(ShapeContainer shape, StridesContainer strides, const T *ptr = nullptr, handle base = handle()); */ return py::array_t( py::array::ShapeContainer({d.nframes, d.nchannels}), // Shape py::array::StridesContainer( // Strides {sizeof(T), sizeof(T) * d.nframes}), // Strides (in bytes) for each index reinterpret_cast( const_cast(d).raw_ptr()), // Pointer to buffer dummyDataOwner // As stated above, now Numpy does not take ownership of // the data pointer. ); } /** * @brief Wraps the InDataHandler such that it calls a Python callback with a * buffer of sample data. The Python callback is called from a different * thread, using a Numpy array as argument. */ class PyIndataHandler : public ThreadedInDataHandler { /** * @brief The callback functions that is called. */ py::function cb, reset_callback; public: PyIndataHandler(StreamMgr &mgr, py::function cb, py::function reset_callback) : ThreadedInDataHandler(mgr), cb(cb), reset_callback(reset_callback) { DEBUGTRACE_ENTER; /// Start should be called externally, as at constructor time no virtual /// functions should be called. /* start(); */ } ~PyIndataHandler() { DEBUGTRACE_ENTER; stop(); } void reset(const Daq *daq) override final { reset_callback(daq); } /** * @brief Reads from the buffer */ bool inCallback_threaded(const DaqData &d) override final { /* DEBUGTRACE_ENTER; */ using DataType = DataTypeDescriptor::DataType; py::gil_scoped_acquire acquire; try { py::object bool_val; switch (d.dtype) { case (DataType::dtype_int8): { bool_val = cb(getPyArrayNoCpy(d)); } break; case (DataType::dtype_int16): { bool_val = cb(getPyArrayNoCpy(d)); } break; case (DataType::dtype_int32): { bool_val = cb(getPyArrayNoCpy(d)); } break; case (DataType::dtype_fl32): { bool_val = cb(getPyArrayNoCpy(d)); } break; case (DataType::dtype_fl64): { bool_val = cb(getPyArrayNoCpy(d)); } break; default: throw std::runtime_error("BUG"); } // End of switch bool res = bool_val.cast(); if (!res) return false; } catch (py::error_already_set &e) { cerr << "ERROR: Python raised exception from callback function: "; cerr << e.what() << endl; return false; } catch (py::cast_error &e) { cerr << e.what() << endl; cerr << "ERROR: Python callback does not return boolean value." << endl; return false; } return true; } }; void init_datahandler(py::module &m) { py::class_ idh(m, "InDataHandler_base"); idh.def("start", &InDataHandler::start); idh.def("stop", &InDataHandler::stop); py::class_ tidh( m, "ThreadedInDataHandler"); /// The C++ class is PyIndataHandler, but for Python, it is called /// InDataHandler py::class_ pyidh(m, "InDataHandler"); pyidh.def(py::init()); /// Peak Programme Meter py::class_ ppm(m, "PPMHandler"); ppm.def(py::init()); ppm.def(py::init()); ppm.def("getCurrentValue", [](const PPMHandler &ppm) { std::tuple tp = ppm.getCurrentValue(); return py::make_tuple(ColToNpy(std::get<0>(tp)), ColToNpy(std::get<1>(tp))); }); /// Real time Aps /// py::class_ rtaps(m, "RtAps"); rtaps.def(py::init(), py::arg("streammgr"), // StreamMgr py::arg("preFilter").none(true), /// Below list of arguments *SHOULD* be same as for /// AvPowerSpectra constructor! py::arg("nfft") = 2048, // py::arg("windowType") = Window::WindowType::Hann, // py::arg("overlap_percentage") = 50.0, // py::arg("time_constant") = -1 // ); rtaps.def("getCurrentValue", [](RtAps &rt) { std::unique_ptr val = rt.getCurrentValue(); if (val) { return CubeToNpy (*val); } return CubeToNpy(ccube(1,0,0)); }); }