2022-10-04 07:27:27 +00:00
|
|
|
/* #define DEBUGTRACE_ENABLED */
|
2022-07-29 07:32:26 +00:00
|
|
|
#include "debugtrace.hpp"
|
2022-10-04 07:48:32 +00:00
|
|
|
#include <carma>
|
2022-10-04 07:27:27 +00:00
|
|
|
#include "lasp_ppm.h"
|
2022-07-29 07:32:26 +00:00
|
|
|
#include "lasp_streammgr.h"
|
2022-08-14 19:00:22 +00:00
|
|
|
#include "lasp_threadedindatahandler.h"
|
2022-07-29 07:32:26 +00:00
|
|
|
#include <atomic>
|
|
|
|
#include <chrono>
|
|
|
|
#include <pybind11/buffer_info.h>
|
|
|
|
#include <pybind11/cast.h>
|
|
|
|
#include <pybind11/gil.h>
|
|
|
|
#include <pybind11/numpy.h>
|
|
|
|
#include <pybind11/pybind11.h>
|
|
|
|
#include <pybind11/pytypes.h>
|
|
|
|
#include <pybind11/stl.h>
|
|
|
|
#include <thread>
|
|
|
|
|
|
|
|
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
|
2022-08-01 15:26:22 +00:00
|
|
|
* data!. Instead, it shares the data from the DaqData container.
|
2022-07-29 07:32:26 +00:00
|
|
|
*
|
|
|
|
* @tparam T The type of the stored sample
|
|
|
|
* @param d The daqdata to convert
|
|
|
|
*
|
|
|
|
* @return Numpy array
|
|
|
|
*/
|
2022-08-14 19:00:22 +00:00
|
|
|
template <typename T> py::array_t<T> getPyArray(const DaqData &d) {
|
2022-07-29 07:32:26 +00:00
|
|
|
// 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 chose "str"...
|
|
|
|
|
|
|
|
py::str dummyDataOwner;
|
|
|
|
/*
|
|
|
|
* Signature:
|
|
|
|
array_t(ShapeContainer shape,
|
|
|
|
StridesContainer strides,
|
|
|
|
const T *ptr = nullptr,
|
|
|
|
handle base = handle());
|
|
|
|
*/
|
|
|
|
|
|
|
|
return py::array_t<T>(
|
2022-08-01 15:26:22 +00:00
|
|
|
py::array::ShapeContainer({d.nframes, d.nchannels}), // Shape
|
2022-08-14 19:00:22 +00:00
|
|
|
|
2022-09-28 07:41:02 +00:00
|
|
|
py::array::StridesContainer( // Strides
|
2022-08-14 19:00:22 +00:00
|
|
|
{sizeof(T),
|
|
|
|
sizeof(T) * d.nframes}), // Strides (in bytes) for each index
|
2022-08-01 15:26:22 +00:00
|
|
|
|
2022-08-14 19:00:22 +00:00
|
|
|
reinterpret_cast<T *>(
|
|
|
|
const_cast<DaqData &>(d).raw_ptr()), // Pointer to buffer
|
2022-08-01 15:26:22 +00:00
|
|
|
|
|
|
|
dummyDataOwner // As stated above, now Numpy does not take ownership of
|
|
|
|
// the data pointer.
|
2022-08-14 19:00:22 +00:00
|
|
|
);
|
2022-07-29 07:32:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Wraps the InDataHandler such that it calls a Python callback with a
|
2022-08-01 15:26:22 +00:00
|
|
|
* buffer of sample data. The Python callback is called from a different
|
2022-10-04 07:27:27 +00:00
|
|
|
* thread, using a Numpy array as argument.
|
2022-07-29 07:32:26 +00:00
|
|
|
*/
|
2022-08-14 19:00:22 +00:00
|
|
|
class PyIndataHandler : public ThreadedInDataHandler {
|
2022-07-29 07:32:26 +00:00
|
|
|
/**
|
2022-10-04 07:27:27 +00:00
|
|
|
* @brief The callback functions that is called.
|
2022-07-29 07:32:26 +00:00
|
|
|
*/
|
2022-10-04 07:27:27 +00:00
|
|
|
py::function cb, reset_callback;
|
2022-07-29 07:32:26 +00:00
|
|
|
|
2022-08-14 19:00:22 +00:00
|
|
|
public:
|
2022-10-04 07:27:27 +00:00
|
|
|
PyIndataHandler(StreamMgr &mgr, py::function cb, py::function reset_callback)
|
|
|
|
: ThreadedInDataHandler(mgr), cb(cb), reset_callback(reset_callback) {
|
2022-08-14 19:00:22 +00:00
|
|
|
|
|
|
|
DEBUGTRACE_ENTER;
|
|
|
|
/// TODO: Note that if start() throws an exception, which means that the
|
|
|
|
/// destructor of PyIndataHandler is not called and the thread destructor
|
|
|
|
/// calls terminate(). It is a kind of rude way to crash, but it is also
|
|
|
|
/// *very* unlikely to happen, as start() does only add a reference to this
|
|
|
|
/// handler to a list in the stream mgr.
|
|
|
|
start();
|
|
|
|
}
|
2022-07-29 07:32:26 +00:00
|
|
|
~PyIndataHandler() {
|
|
|
|
DEBUGTRACE_ENTER;
|
|
|
|
stop();
|
|
|
|
}
|
2022-10-04 07:27:27 +00:00
|
|
|
void reset(const Daq *daq) override final { reset_callback(daq); }
|
2022-07-29 07:32:26 +00:00
|
|
|
|
|
|
|
/**
|
2022-08-14 19:00:22 +00:00
|
|
|
* @brief Reads from the buffer
|
2022-07-29 07:32:26 +00:00
|
|
|
*/
|
2022-10-04 07:27:27 +00:00
|
|
|
bool inCallback_threaded(const DaqData &d) override final {
|
2022-07-29 07:32:26 +00:00
|
|
|
|
|
|
|
/* DEBUGTRACE_ENTER; */
|
|
|
|
|
|
|
|
using DataType = DataTypeDescriptor::DataType;
|
|
|
|
|
2022-08-14 19:00:22 +00:00
|
|
|
py::gil_scoped_acquire acquire;
|
|
|
|
try {
|
2022-09-28 07:41:02 +00:00
|
|
|
py::object bool_val;
|
2022-08-14 19:00:22 +00:00
|
|
|
switch (d.dtype) {
|
2022-09-28 07:41:02 +00:00
|
|
|
case (DataType::dtype_int8): {
|
|
|
|
bool_val = cb(getPyArray<int8_t>(d));
|
|
|
|
} break;
|
|
|
|
case (DataType::dtype_int16): {
|
|
|
|
bool_val = cb(getPyArray<int16_t>(d));
|
|
|
|
} break;
|
|
|
|
case (DataType::dtype_int32): {
|
|
|
|
bool_val = cb(getPyArray<int32_t>(d));
|
|
|
|
} break;
|
|
|
|
case (DataType::dtype_fl32): {
|
|
|
|
bool_val = cb(getPyArray<float>(d));
|
|
|
|
} break;
|
|
|
|
case (DataType::dtype_fl64): {
|
|
|
|
bool_val = cb(getPyArray<double>(d));
|
|
|
|
} break;
|
2022-08-14 19:00:22 +00:00
|
|
|
default:
|
|
|
|
throw std::runtime_error("BUG");
|
|
|
|
} // End of switch
|
|
|
|
|
|
|
|
bool res = bool_val.cast<bool>();
|
|
|
|
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;
|
2022-07-29 07:32:26 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
void init_datahandler(py::module &m) {
|
|
|
|
py::class_<PyIndataHandler> h(m, "InDataHandler");
|
2022-10-04 07:27:27 +00:00
|
|
|
h.def(py::init<StreamMgr &, py::function, py::function>());
|
2022-07-29 07:32:26 +00:00
|
|
|
|
2022-10-04 07:27:27 +00:00
|
|
|
py::class_<PPMHandler> ppm(m, "PPMHandler");
|
|
|
|
ppm.def(py::init<StreamMgr &, const d>());
|
|
|
|
ppm.def(py::init<StreamMgr &>());
|
|
|
|
|
|
|
|
ppm.def("getCurrentValue", [](const PPMHandler &ppm) {
|
|
|
|
auto [level, clip] = ppm.getCurrentValue();
|
|
|
|
|
2022-10-04 07:48:32 +00:00
|
|
|
return py::make_tuple(carma::col_to_arr(std::move(level)),
|
|
|
|
carma::col_to_arr(std::move(clip)));
|
2022-10-04 07:27:27 +00:00
|
|
|
});
|
2022-07-29 07:32:26 +00:00
|
|
|
}
|