LASP 1.0
Library for Acoustic Signal Processing
Loading...
Searching...
No Matches
lasp_pyindatahandler.cpp
Go to the documentation of this file.
1/* #define DEBUGTRACE_ENABLED */
2#include "arma_npy.h"
3#include "debugtrace.hpp"
4#include "lasp_clip.h"
5#include "lasp_daq.h"
6#include "lasp_daqdata.h"
7#include "lasp_ppm.h"
8#include "lasp_rtaps.h"
10#include "lasp_streammgr.h"
12#include <armadillo>
13#include <atomic>
14#include <chrono>
15#include <pybind11/pybind11.h>
16
17using namespace std::literals::chrono_literals;
18using std::cerr;
19using std::endl;
20
21namespace py = pybind11;
22
32template <typename T, bool copy = false>
33py::array_t<T> getPyArrayNoCpy(const DaqData &d) {
34 // https://github.com/pybind/pybind11/issues/323
35 //
36 // When a valid object is passed as 'base', it tells pybind not to take
37 // ownership of the data, because 'base' will own it. In fact 'packet' will
38 // own it, but - psss! - , we don't tell it to pybind... Alos note that ANY
39 // valid object is good for this purpose, so I choose "str"...
40
41 py::str dummyDataOwner;
42 /*
43 * Signature:
44 array_t(ShapeContainer shape,
45 StridesContainer strides,
46 const T *ptr = nullptr,
47 handle base = handle());
48 */
49
50 return py::array_t<T>(
51 py::array::ShapeContainer({d.nframes, d.nchannels}), // Shape
52
53 py::array::StridesContainer( // Strides
54 {sizeof(T),
55 sizeof(T) * d.nframes}), // Strides (in bytes) for each index
56
57 reinterpret_cast<T *>(
58 const_cast<DaqData &>(d).raw_ptr()), // Pointer to buffer
59
60 dummyDataOwner // As stated above, now Numpy does not take ownership of
61 // the data pointer.
62 );
63}
64
65template <typename T, bool copy = false>
66py::array_t<d> dmat_to_ndarray(const DaqData &d) {
67 // https://github.com/pybind/pybind11/issues/323
68 //
69 // When a valid object is passed as 'base', it tells pybind not to take
70 // ownership of the data, because 'base' will own it. In fact 'packet' will
71 // own it, but - psss! - , we don't tell it to pybind... Alos note that ANY
72 // valid object is good for this purpose, so I choose "str"...
73
74 py::str dummyDataOwner;
75 /*
76 * Signature:
77 array_t(ShapeContainer shape,
78 StridesContainer strides,
79 const T *ptr = nullptr,
80 handle base = handle());
81 */
82
83 return py::array_t<T>(
84 py::array::ShapeContainer({d.nframes, d.nchannels}), // Shape
85
86 py::array::StridesContainer( // Strides
87 {sizeof(T),
88 sizeof(T) * d.nframes}), // Strides (in bytes) for each index
89
90 reinterpret_cast<T *>(
91 const_cast<DaqData &>(d).raw_ptr()), // Pointer to buffer
92
93 dummyDataOwner // As stated above, now Numpy does not take ownership of
94 // the data pointer.
95 );
96}
97
103class PyIndataHandler : public ThreadedInDataHandler<PyIndataHandler> {
107 py::function cb, reset_callback;
108
109public:
119 PyIndataHandler(SmgrHandle mgr, py::function cb, py::function reset_callback)
120 : ThreadedInDataHandler(mgr), cb(cb), reset_callback(reset_callback) {
121
122 DEBUGTRACE_ENTER;
125 py::gil_scoped_release release;
126 startThread();
127 }
129 DEBUGTRACE_ENTER;
132 py::gil_scoped_release release;
133 stopThread();
134 }
140 void reset(const Daq *daq) {
141 DEBUGTRACE_ENTER;
142 try {
143 py::gil_scoped_acquire acquire;
144 if (daq) {
145 reset_callback(daq);
146 } else {
147 reset_callback(py::none());
148 }
149 } catch (py::error_already_set &e) {
150 cerr << "*************** Error calling reset callback!\n";
151 cerr << e.what() << endl;
152 cerr << "*************** \n";
155 abort();
156 /* throw std::runtime_error(e.what()); */
157 } catch (std::exception &e) {
158 cerr << "Caught unknown exception in reset callback:" << e.what() << endl;
159 abort();
160 }
161 }
162
167 void inCallback(const DaqData &d) {
168
169 /* DEBUGTRACE_ENTER; */
170
171 using DataType = DataTypeDescriptor::DataType;
172
173 try {
174 py::gil_scoped_acquire acquire;
175 py::object bool_val;
176 switch (d.dtype) {
177 case (DataType::dtype_int8): {
178 bool_val = cb(getPyArrayNoCpy<int8_t>(d));
179 } break;
180 case (DataType::dtype_int16): {
181 bool_val = cb(getPyArrayNoCpy<int16_t>(d));
182 } break;
183 case (DataType::dtype_int32): {
184 bool_val = cb(getPyArrayNoCpy<int32_t>(d));
185 } break;
186 case (DataType::dtype_fl32): {
187 bool_val = cb(getPyArrayNoCpy<float>(d));
188 } break;
189 case (DataType::dtype_fl64): {
190 bool_val = cb(getPyArrayNoCpy<double>(d));
191 } break;
192 default:
193 throw std::runtime_error("BUG");
194 } // End of switch
195
196 bool res = bool_val.cast<bool>();
197 } catch (py::error_already_set &e) {
198 cerr << "ERROR: Python raised exception from callback function: ";
199 cerr << e.what() << endl;
200 abort();
201 } catch (py::cast_error &e) {
202 cerr << e.what() << endl;
203 cerr << "ERROR: Python callback does not return boolean value." << endl;
204 abort();
205 } catch (std::exception &e) {
206 cerr << "Caught unknown exception in Python callback:" << e.what()
207 << endl;
208 abort();
209 }
210 }
211};
212
213void init_datahandler(py::module &m) {
214
217 py::class_<PyIndataHandler> pyidh(m, "InDataHandler");
218 pyidh.def(py::init<SmgrHandle, py::function, py::function>());
219
221 py::class_<PPMHandler> ppm(m, "PPMHandler");
222 ppm.def(py::init<SmgrHandle, const d>());
223 ppm.def(py::init<SmgrHandle>());
224
225 ppm.def("getCurrentValue", [](const PPMHandler &ppm) {
226 std::tuple<vd, arma::uvec> tp;
227 {
228 py::gil_scoped_release release;
229 tp = ppm.getCurrentValue();
230 }
231
232 return py::make_tuple(ColToNpy<d>(std::get<0>(tp)),
233 ColToNpy<arma::uword>(std::get<1>(tp)));
234 });
235
237 py::class_<ClipHandler> clip(m, "ClipHandler");
238 clip.def(py::init<SmgrHandle>());
239
240 clip.def("getCurrentValue", [](const ClipHandler &clip) {
241 arma::uvec cval;
242 {
243 py::gil_scoped_release release;
244 cval = clip.getCurrentValue();
245 }
246
247 return ColToNpy<arma::uword>(cval); // something goes wrong here
248 });
249
252 py::class_<RtAps> rtaps(m, "RtAps");
253 rtaps.def(py::init<SmgrHandle, // StreamMgr
254 Filter *const, // FreqWeighting filter
255 const us, // Nfft
256 const Window::WindowType, // Window
257 const d, // Overlap percentage 0<=o<100
258
259 const d // Time constant
260 >(),
261 py::arg("streammgr"), // StreamMgr
262 py::arg("preFilter").none(true),
264
266 py::arg("nfft") = 2048, //
267 py::arg("windowType") = Window::WindowType::Hann, //
268 py::arg("overlap_percentage") = 50.0, //
269 py::arg("time_constant") = -1 //
270 );
271
272 rtaps.def("getCurrentValue", [](RtAps &rt) {
273 ccube val;
274 {
275 py::gil_scoped_release release;
276 val = rt.getCurrentValue();
277 }
278 return CubeToNpy<c>(val);
279 });
280
283 py::class_<RtSignalViewer> rtsv(m, "RtSignalViewer");
284 rtsv.def(py::init<SmgrHandle, // StreamMgr
285 const d, // Time history
286 const us, // Resolution
287 const us // Channel number
288 >());
289
290 rtsv.def("getCurrentValue", [](RtSignalViewer &rt) {
291 dmat val;
292 {
293 py::gil_scoped_release release;
294 val = rt.getCurrentValue();
295 }
296 return MatToNpy<d>(val);
297 });
298}
Clipping detector (Clip). Detects when a signal overdrives the input.
Definition lasp_clip.h:24
Data coming from / going to DAQ. Non-interleaved format, which means data in buffer is ordered by cha...
Base cass for all DAQ (Data Acquisition) interfaces. A DAQ can be a custom device,...
Definition lasp_daq.h:29
DataType
Basic data types coming from a DAQ that we can deal with. The naming will be self-explainging.
Filter used to pre-filter a double-precision floating point data stream.
Definition lasp_filter.h:10
Digital Peak Programme Meter (PPM). Let the latest maximum flow away with a certain amount of dB/s....
Definition lasp_ppm.h:25
Wraps the ThreadedInDataHandler such that it calls a Python callback with a buffer of sample data....
void inCallback(const DaqData &d)
Calls the Python callback method / function with a Numpy array of stream data.
void reset(const Daq *daq)
Calls the reset callback in Python.
PyIndataHandler(SmgrHandle mgr, py::function cb, py::function reset_callback)
Initialize PyIndataHandler.
Real time spectral estimator using Welch method of spectral estimation.
Definition lasp_rtaps.h:26
ccube getCurrentValue() const
Get the latest estimate of the power spectra.
Real time signal viewer. Shows envelope of the signal based on amount of history shown.
void startThread()
This method should be called from the derived class' constructor, to start the thread and data is inc...
void stopThread()
This method SHOULD be called from all classes that derive on ThreadedInDataHandler....
A bit of curiously recurring template pattern, to connect the specific handlers and connect the prope...
void init_datahandler(py::module &m)
std::shared_ptr< StreamMgr > SmgrHandle
arma::Mat< d > dmat
arma::Cube< c > ccube
py::array_t< T > getPyArrayNoCpy(const DaqData &d)
Generate a Numpy array from daqdata, does NOT create a copy of the data!. Instead,...
py::array_t< d > dmat_to_ndarray(const DaqData &d)
size_t us
We often use boolean values.
Definition lasp_types.h:29