|
From: dan n. <dne...@ya...> - 2019-08-25 23:22:28
|
Hello Marc, I agree. I think option 2 is the best strategy going forward. This will change the existing niDAQmx plugin so it is incompatible with the one that exists now. This will give me the opportunity to fix the hack that I used to delay setting task parameters until channel parameters are set. This would change the order of the commands in the plugin lifetime to: create plugin instanceset ChannelParameters (one setParam for each channel in the task)set TaskParameters startDevice <execute the necessary commands to get the data. This differs depending on whether finite or continuous input is selected. But,, see my comments below on using a callback function> stopDevicedelete plugin instance. Have you given any thought to how itom should handle major versions of plugins? If we adopt option two, there will be 3 versions of niDAQmx: 1) the current version, 2) a new version for which each instance is tied to only one device and implements option 2 semantics, and 3) the version you are working on that supports multiple devices per plugin instance. We could distinguish plugin versions with a naming convention, i.e., niDAQmx_1, niDAQmx_2, and niDAQmx_3. This would also support minor and micro versioning, i.e., niDAQmx_2.2.3. It would also allow us to build versions of the plugin that support different versions of the niDAQmxl C library (right now I am using 18.1, but 19.1 was published in July). Of course that would complicate the CMake files, but that is work that would have substantial benefits. I have been investigating the use the callback function in reading channels. Here are some thoughts: 1. It turns out that use of the callback function would have benefits not only for continuous, but also finite analog input. For example, I have regularly run phase noise experiments using my PicoScope that required 52 seconds of data per segment. Each of these segments is averaged together to produce a smooth FFT plot. For the experiments I ran, the parameters were: 10,000 samples per second, 52 seconds per segment, 30 segments per FFT plot. The niDAQmx library only supports 64-bit floating point for sampling using real numbers. So each sample is 8 bytes long, meaning an equivalent FFT computation would require: 10,000*52*30*8 bytes of data = 124 GB of data. This obviously won't fit into my computer memory (which only has 1 GB of free memory). The way the PicoScope handles this is to sample 16-bits and move each segment to the main machine for averaging (the PicoScope is a USB Oscilloscope/Spectrum Analyzer). This reduces the amount of data that the PicoScope must hold in hardware to 4.1 MB of data (per segment). If the niDAQmx plugin was suitably modified, I also could sample at 16-bits and then do the averaging and conversion to floating point in itom. But, this would significantly increase the processing in itom. Since Python is an interpreted language, it is not optimal for high intensity numerical calculations. So, if I wanted to do this experiment using itom and niDAQmx, I would probably want to write the segment averaging and floating point conversion in C or C++. 2. The callback function in niDAQmx executes in the context of the niDAQmx thread. There is an option for using a user-land thread as the context for the callback function, but that only works for Windows, it isn't supported for Linux. Consequently, to keep itom platform independent, the callback must execute in the niDAQmx thread. Now I am not completely certain, but since niDAQmx is driver software, I assume it is using kernel threads. You don't want to tie up a kernel thread with a lot of computation, since this delays critical functions in the kernel. So, the activity of the callback function should be as lightweight as possible, which means simply getting the data out of the driver and into user-land memory. Then the computationally intense activity would be implemented by a user-land (i.e., itom thread). 3. Given the above, what does the itom thread need to do? For the problem outlined above, it needs to average the segments. This requires converting the voltage readings to power units, which means first converting to floating point. Then run the FFT algorithm (along with preliminary filtering and windowing) to get the bins over which averaging occurs. Then, on a per segment basis, adding the bins together and dividing by the number of segments (this can be done on an iterative basis, so you don't have to process all of the segments at once). This is activity using finite analog input. So, I think I have made at least one case why using the callback function in the niDAQmx C function suite would greatly benefit when finite analog input is selected. We should discuss this, but if we decide to allow callbacks for both continuous and finite analog input, we can make the interface a bit more symmetric. Both continuous and finite analog input would always use the callback technique. StartDevice and StopDevice would always be used and acquire() become a noop. getVal or copyVal then would simply move the data sitting in the user-land buffers into a dataObject (either using a shallow or deep copy). Comments? Regards, Dan On Aug 25, 2019, at 9:47 AM, Marc Gronle <mar...@it...> wrote: Hi Dan, you asked exactly the same questions that I am asking myself since a while. I guess all following options have one thing in common: If a continuous input is started, it is the responsibility of the user to regularily request a chunk of already recorded data from the plugin (and optionally store this chunk to a file, visualize it...). Therefore getVal should be regularily called and the returned dataObject always has a changing number of columns, depending on the number of samples that have been collected since the last call of getVal. If I understand the NI documentation properly, the task will stop and indicate an error, if the internal buffer (given by the number of samples in the task configuration) is full and nobody cleared it by calling DAQmxReadAnalogF64 (or something similar). Currently, we don't have a similar device with continuous acquisition. There is only one other supported AD/DA device from measurement computing, but the continuous acquisitionis not implemented there, too. Therefore, there is no established infrastructure available in itom for this (not yet). I see the following options: 1. The quick and dirty solution (I don't like it to much):- startDevice / stopDevice are NOOP- acquire will start the continuous task or finite task - a continuous call to getVal will always obtain the currently available chunk of data in the internal buffer. The task will automatically stop if you don't call getVal again or if you restart the task with another paramerization. For finite tasks, getVal blocks until all samples are recorded (or timeout) 2. The solution, which fits best to other IO-devices like cameras- starting a task will be moved to startDevice (for cameras, startDevice will always bring a camera into a state, where images can then be taken, each single acquisition is then triggered by 'acquire', once a device is started, not all parameters can be changed - this highly depends on the camera) - stopping the task will be moved to stopDevice- If the task is continuous, it will be really started by calling startDevice (acquire is Noop then), if the task is finite, acquire has to be called to fire the start trigger- for a finite task, getVal blocks until all samples are recorded (or timeout), for a continuous task one has to regularily call getVal such that the internal NI buffer never gets full (and the task is interrupted then) 3. AD/DA specific solution- startDevice / stopDevice are NOOP- acquire will start a continuous or finite task- there will be a new method in the base class ito::AddInDataIO (e.g. stopAcquisition or simply stop) which will be wrapped by a method with the same name in the python class itom.dataIO which is only implemented in the case of AD/DA converters which can be called to stop a continuous task- the behaviour of getVal is again the same than in option 1 or 2 Me personally, I would prefer option 2, since startDevice and stopDevice are currently unused for the NIDAQmx plugin, but for cameras, which only provide a continuous data stream and cannot be triggered, like webcams, startDevice will start the data stream and acquire will only "extract" the latest image in the stream and provide it to the following getVal command. Introducing a new command will be available to the dataIO interface in python, hence every camera would have this command which is a NOOP then. All in all, my ranking would be option 2, then option 3 and option 1 would only be a workaround. The NIDAQmx interface also has the option to register a callback function if a certain number of samples have been recorded. In one of my latest emails to you, I talked about the official python package nidaqmx-python and the example, where one registers a python method as callback. Inside of this callback, the current available chunk of data is obtained (to a numpy-array) and stored to the hard drive. In theory it would also be possible to register a c-function in the c++ plugin to this callback function and if it is fired emit a Qt signal, defined in the plugin. Since a couple of months it is now possible to connect python methods in itom to such a function. However you still have to call getVal to really read the data and store it. Therefore I would say that it is often possible to estimate the right time interval when data has been obtained to avoid a full NI-internal buffer. Then it is also possible to repeatedly call getVal within a loop and a sleep command or to use the python class itom.timer (or similar ones) to register a callback function in python which should be called e.g. every 250ms. Best regards Marc Am Sa., 24. Aug. 2019 um 17:20 Uhr schrieb dan nessett <dne...@ya...>: Hi Marc, I am starting to think about how to implement continuous analog input. I think one point you have raised is how to signal the beginning of the input and the end of it. It would be possible to use acquire() to start the input and stopDevice() to end it, but that has the feeling of a hack (overloading existing functions to do something else). Another possibility would be to add startChannel() and stopChannel() methods to the plugin. Is there a way to add plugin specific method calls so they have python wrappers? Are there any other plugins doing this (if so, would you point me to them so I can take a look at the code)? Cheers, Dan |