2022-10-20 10:28:46 +00:00
|
|
|
/* #define DEBUGTRACE_ENABLED */
|
2022-10-11 07:43:36 +00:00
|
|
|
#include "arma_npy.h"
|
2022-10-06 19:13:21 +00:00
|
|
|
#include "debugtrace.hpp"
|
2023-01-27 13:56:46 +00:00
|
|
|
#include "lasp_clip.h"
|
2023-06-07 19:49:07 +00:00
|
|
|
#include "lasp_daq.h"
|
|
|
|
#include "lasp_daqdata.h"
|
|
|
|
#include "lasp_ppm.h"
|
2022-10-06 19:13:21 +00:00
|
|
|
#include "lasp_rtaps.h"
|
2023-01-05 09:35:47 +00:00
|
|
|
#include "lasp_rtsignalviewer.h"
|
2023-06-06 14:05:24 +00:00
|
|
|
#include "lasp_streammgr.h"
|
2023-06-07 19:49:07 +00:00
|
|
|
#include "lasp_threadedindatahandler.h"
|
2023-01-05 09:35:47 +00:00
|
|
|
#include <armadillo>
|
2022-07-29 07:32:26 +00:00
|
|
|
#include <atomic>
|
|
|
|
#include <chrono>
|
|
|
|
#include <pybind11/pybind11.h>
|
|
|
|
|
|
|
|
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-10-11 07:43:36 +00:00
|
|
|
template <typename T, bool copy = false>
|
|
|
|
py::array_t<T> getPyArrayNoCpy(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
|
2022-10-10 17:17:38 +00:00
|
|
|
// 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<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<T *>(
|
|
|
|
const_cast<DaqData &>(d).raw_ptr()), // Pointer to buffer
|
|
|
|
|
|
|
|
dummyDataOwner // As stated above, now Numpy does not take ownership of
|
|
|
|
// the data pointer.
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-10-11 07:43:36 +00:00
|
|
|
template <typename T, bool copy = false>
|
|
|
|
py::array_t<d> dmat_to_ndarray(const DaqData &d) {
|
2022-10-10 17:17:38 +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 choose "str"...
|
2022-07-29 07:32:26 +00:00
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-06-20 15:08:55 +00:00
|
|
|
* @brief Wraps the ThreadedInDataHandler such that it calls a Python callback
|
|
|
|
* with a buffer of sample data. Converts DaqData objects to Numpy arrays and
|
|
|
|
* calls Python given as argument to the constructor
|
2022-07-29 07:32:26 +00:00
|
|
|
*/
|
2023-06-09 08:43:04 +00:00
|
|
|
class PyIndataHandler : public ThreadedInDataHandler<PyIndataHandler> {
|
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
|
|
|
*/
|
2024-03-06 20:41:04 +00:00
|
|
|
std::unique_ptr<py::function> cb, reset_callback;
|
|
|
|
bool _done{false};
|
2022-07-29 07:32:26 +00:00
|
|
|
|
2024-03-06 20:41:04 +00:00
|
|
|
public:
|
2023-06-09 08:43:04 +00:00
|
|
|
/**
|
|
|
|
* @brief Initialize PyIndataHandler
|
|
|
|
*
|
|
|
|
* @param mgr StreamMgr handle
|
|
|
|
* @param cb Python callback that is called with Numpy input data from device
|
|
|
|
* @param reset_callback Python callback that is called with a Daq pointer.
|
|
|
|
* Careful: do not store this handle, as it is only valid as long as reset()
|
|
|
|
* is called, when a stream stops, this pointer / handle will dangle.
|
|
|
|
*/
|
2023-06-07 19:49:07 +00:00
|
|
|
PyIndataHandler(SmgrHandle mgr, py::function cb, py::function reset_callback)
|
2024-03-06 20:41:04 +00:00
|
|
|
: ThreadedInDataHandler(mgr),
|
|
|
|
cb(std::make_unique<py::function>(cb)),
|
|
|
|
reset_callback(std::make_unique<py::function>(reset_callback)) {
|
2022-08-14 19:00:22 +00:00
|
|
|
DEBUGTRACE_ENTER;
|
2022-10-10 17:17:38 +00:00
|
|
|
/// Start should be called externally, as at constructor time no virtual
|
|
|
|
/// functions should be called.
|
2023-06-20 15:08:55 +00:00
|
|
|
py::gil_scoped_release release;
|
2023-06-06 14:05:24 +00:00
|
|
|
startThread();
|
2022-08-14 19:00:22 +00:00
|
|
|
}
|
2022-07-29 07:32:26 +00:00
|
|
|
~PyIndataHandler() {
|
|
|
|
DEBUGTRACE_ENTER;
|
2023-06-20 15:08:55 +00:00
|
|
|
/// Callback cannot be called, which results in a deadlock on the GIL
|
|
|
|
/// without this release.
|
|
|
|
py::gil_scoped_release release;
|
2023-06-06 14:05:24 +00:00
|
|
|
stopThread();
|
2022-07-29 07:32:26 +00:00
|
|
|
}
|
2023-06-09 08:43:04 +00:00
|
|
|
/**
|
|
|
|
* @brief Calls the reset callback in Python.
|
|
|
|
*
|
|
|
|
* @param daq Daq device, or nullptr in case no input stream is running.
|
|
|
|
*/
|
|
|
|
void reset(const Daq *daq) {
|
2023-06-07 19:49:07 +00:00
|
|
|
DEBUGTRACE_ENTER;
|
2024-03-06 20:41:04 +00:00
|
|
|
if (_done) return;
|
2023-06-07 19:49:07 +00:00
|
|
|
try {
|
2023-06-20 15:16:56 +00:00
|
|
|
py::gil_scoped_acquire acquire;
|
2023-06-07 19:49:07 +00:00
|
|
|
if (daq) {
|
2024-03-06 20:41:04 +00:00
|
|
|
(*reset_callback)(daq);
|
2023-06-07 19:49:07 +00:00
|
|
|
} else {
|
2024-03-06 20:41:04 +00:00
|
|
|
(*reset_callback)(py::none());
|
2023-06-07 19:49:07 +00:00
|
|
|
}
|
|
|
|
} catch (py::error_already_set &e) {
|
|
|
|
cerr << "*************** Error calling reset callback!\n";
|
|
|
|
cerr << e.what() << endl;
|
|
|
|
cerr << "*************** \n";
|
|
|
|
/// Throwing a runtime error here does not work out one way or another.
|
|
|
|
/// Therefore, it is better to dive out and prevent undefined behaviour
|
|
|
|
abort();
|
|
|
|
/* throw std::runtime_error(e.what()); */
|
2023-06-20 15:16:56 +00:00
|
|
|
} catch (std::exception &e) {
|
|
|
|
cerr << "Caught unknown exception in reset callback:" << e.what() << endl;
|
|
|
|
abort();
|
2023-06-07 19:49:07 +00:00
|
|
|
}
|
|
|
|
}
|
2022-07-29 07:32:26 +00:00
|
|
|
|
|
|
|
/**
|
2023-06-09 08:43:04 +00:00
|
|
|
* @brief Calls the Python callback method / function with a Numpy array of
|
|
|
|
* stream data.
|
2022-07-29 07:32:26 +00:00
|
|
|
*/
|
2023-06-09 08:43:04 +00:00
|
|
|
void inCallback(const DaqData &d) {
|
2022-07-29 07:32:26 +00:00
|
|
|
|
|
|
|
/* DEBUGTRACE_ENTER; */
|
|
|
|
|
|
|
|
using DataType = DataTypeDescriptor::DataType;
|
2024-03-06 20:41:04 +00:00
|
|
|
if (_done) return;
|
2022-07-29 07:32:26 +00:00
|
|
|
|
2022-08-14 19:00:22 +00:00
|
|
|
try {
|
2023-06-20 15:08:55 +00:00
|
|
|
py::gil_scoped_acquire acquire;
|
2022-09-28 07:41:02 +00:00
|
|
|
py::object bool_val;
|
2022-08-14 19:00:22 +00:00
|
|
|
switch (d.dtype) {
|
2024-03-06 20:41:04 +00:00
|
|
|
case (DataType::dtype_int8): {
|
|
|
|
bool_val = (*cb)(getPyArrayNoCpy<int8_t>(d));
|
|
|
|
} break;
|
|
|
|
case (DataType::dtype_int16): {
|
|
|
|
bool_val = (*cb)(getPyArrayNoCpy<int16_t>(d));
|
|
|
|
} break;
|
|
|
|
case (DataType::dtype_int32): {
|
|
|
|
bool_val = (*cb)(getPyArrayNoCpy<int32_t>(d));
|
|
|
|
} break;
|
|
|
|
case (DataType::dtype_fl32): {
|
|
|
|
bool_val = (*cb)(getPyArrayNoCpy<float>(d));
|
|
|
|
} break;
|
|
|
|
case (DataType::dtype_fl64): {
|
|
|
|
bool_val = (*cb)(getPyArrayNoCpy<double>(d));
|
|
|
|
} break;
|
|
|
|
default:
|
|
|
|
throw std::runtime_error("BUG");
|
|
|
|
} // End of switch
|
2022-08-14 19:00:22 +00:00
|
|
|
|
|
|
|
bool res = bool_val.cast<bool>();
|
2024-03-06 20:41:04 +00:00
|
|
|
if (res == false) {
|
|
|
|
DEBUGTRACE_PRINT("Setting callbacks to None")
|
|
|
|
_done = true;
|
|
|
|
// cb = py::function(py::none());
|
|
|
|
// reset_callback = py::function(py::none());
|
|
|
|
cb.reset();
|
|
|
|
reset_callback.reset();
|
|
|
|
}
|
2022-08-14 19:00:22 +00:00
|
|
|
} catch (py::error_already_set &e) {
|
|
|
|
cerr << "ERROR: Python raised exception from callback function: ";
|
|
|
|
cerr << e.what() << endl;
|
2023-06-09 08:43:04 +00:00
|
|
|
abort();
|
2022-08-14 19:00:22 +00:00
|
|
|
} catch (py::cast_error &e) {
|
|
|
|
cerr << e.what() << endl;
|
|
|
|
cerr << "ERROR: Python callback does not return boolean value." << endl;
|
2023-06-09 08:43:04 +00:00
|
|
|
abort();
|
2023-06-20 15:08:55 +00:00
|
|
|
} catch (std::exception &e) {
|
2023-06-20 15:16:56 +00:00
|
|
|
cerr << "Caught unknown exception in Python callback:" << e.what()
|
|
|
|
<< endl;
|
2023-06-20 15:08:55 +00:00
|
|
|
abort();
|
2022-07-29 07:32:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2022-10-06 19:13:21 +00:00
|
|
|
|
2022-07-29 07:32:26 +00:00
|
|
|
void init_datahandler(py::module &m) {
|
2022-10-10 17:17:38 +00:00
|
|
|
|
|
|
|
/// The C++ class is PyIndataHandler, but for Python, it is called
|
|
|
|
/// InDataHandler
|
2023-06-06 14:05:24 +00:00
|
|
|
py::class_<PyIndataHandler> pyidh(m, "InDataHandler");
|
2023-06-07 19:49:07 +00:00
|
|
|
pyidh.def(py::init<SmgrHandle, py::function, py::function>());
|
2022-07-29 07:32:26 +00:00
|
|
|
|
2022-10-06 19:13:21 +00:00
|
|
|
/// Peak Programme Meter
|
2023-06-06 14:05:24 +00:00
|
|
|
py::class_<PPMHandler> ppm(m, "PPMHandler");
|
2023-06-07 19:49:07 +00:00
|
|
|
ppm.def(py::init<SmgrHandle, const d>());
|
|
|
|
ppm.def(py::init<SmgrHandle>());
|
2022-10-04 07:27:27 +00:00
|
|
|
|
|
|
|
ppm.def("getCurrentValue", [](const PPMHandler &ppm) {
|
2023-06-20 15:16:56 +00:00
|
|
|
std::tuple<vd, arma::uvec> tp;
|
|
|
|
{
|
|
|
|
py::gil_scoped_release release;
|
|
|
|
tp = ppm.getCurrentValue();
|
|
|
|
}
|
2022-10-04 07:27:27 +00:00
|
|
|
|
2022-10-11 07:43:36 +00:00
|
|
|
return py::make_tuple(ColToNpy<d>(std::get<0>(tp)),
|
2023-01-05 09:35:47 +00:00
|
|
|
ColToNpy<arma::uword>(std::get<1>(tp)));
|
2022-10-06 19:13:21 +00:00
|
|
|
});
|
|
|
|
|
2023-01-27 13:56:46 +00:00
|
|
|
/// Clip Detector
|
2023-06-06 14:05:24 +00:00
|
|
|
py::class_<ClipHandler> clip(m, "ClipHandler");
|
2023-06-07 19:49:07 +00:00
|
|
|
clip.def(py::init<SmgrHandle>());
|
2023-01-27 13:56:46 +00:00
|
|
|
|
|
|
|
clip.def("getCurrentValue", [](const ClipHandler &clip) {
|
2023-06-20 15:16:56 +00:00
|
|
|
arma::uvec cval;
|
|
|
|
{
|
|
|
|
py::gil_scoped_release release;
|
|
|
|
cval = clip.getCurrentValue();
|
|
|
|
}
|
2023-01-27 13:56:46 +00:00
|
|
|
|
|
|
|
return ColToNpy<arma::uword>(cval); // something goes wrong here
|
|
|
|
});
|
|
|
|
|
2022-10-06 19:13:21 +00:00
|
|
|
/// Real time Aps
|
|
|
|
///
|
2023-06-06 14:05:24 +00:00
|
|
|
py::class_<RtAps> rtaps(m, "RtAps");
|
2023-06-07 19:49:07 +00:00
|
|
|
rtaps.def(py::init<SmgrHandle, // StreamMgr
|
2022-10-06 19:13:21 +00:00
|
|
|
Filter *const, // FreqWeighting filter
|
|
|
|
const us, // Nfft
|
|
|
|
const Window::WindowType, // Window
|
|
|
|
const d, // Overlap percentage 0<=o<100
|
|
|
|
|
|
|
|
const d // Time constant
|
|
|
|
>(),
|
|
|
|
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) {
|
2023-06-20 15:16:56 +00:00
|
|
|
ccube val;
|
|
|
|
{
|
|
|
|
py::gil_scoped_release release;
|
|
|
|
val = rt.getCurrentValue();
|
|
|
|
}
|
2023-01-05 09:35:47 +00:00
|
|
|
return CubeToNpy<c>(val);
|
|
|
|
});
|
|
|
|
|
|
|
|
/// Real time Signal Viewer
|
|
|
|
///
|
2023-06-06 14:05:24 +00:00
|
|
|
py::class_<RtSignalViewer> rtsv(m, "RtSignalViewer");
|
2023-06-07 19:49:07 +00:00
|
|
|
rtsv.def(py::init<SmgrHandle, // StreamMgr
|
|
|
|
const d, // Time history
|
|
|
|
const us, // Resolution
|
|
|
|
const us // Channel number
|
2023-01-05 09:35:47 +00:00
|
|
|
>());
|
|
|
|
|
|
|
|
rtsv.def("getCurrentValue", [](RtSignalViewer &rt) {
|
2023-06-20 15:16:56 +00:00
|
|
|
dmat val;
|
|
|
|
{
|
|
|
|
py::gil_scoped_release release;
|
|
|
|
val = rt.getCurrentValue();
|
|
|
|
}
|
2023-01-05 09:35:47 +00:00
|
|
|
return MatToNpy<d>(val);
|
2022-10-04 07:27:27 +00:00
|
|
|
});
|
2022-07-29 07:32:26 +00:00
|
|
|
}
|