Skip to content

Commit c1e9869

Browse files
mikeprosserniMike Prosser
andauthored
Add task.read_waveform() for analog inputs (#807)
* add task.read_waveform() for analog inputs * test___analog_multi_channel_finite___read_waveform_too_many_samples___returns_waveforms_with_correct_number_of_samples * address feedback --------- Co-authored-by: Mike Prosser <[email protected]>
1 parent 623e5cb commit c1e9869

File tree

8 files changed

+428
-21
lines changed

8 files changed

+428
-21
lines changed

examples/analog_in/voltage_acq_int_clk_wfm.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,13 @@
99
os.environ["NIDAQMX_ENABLE_WAVEFORM_SUPPORT"] = "1"
1010

1111
import nidaqmx # noqa: E402 # Must import after setting environment variable
12-
from nidaqmx.constants import AcquisitionType, READ_ALL_AVAILABLE # noqa: E402
13-
from nidaqmx.stream_readers import AnalogSingleChannelReader # noqa: E402
12+
from nidaqmx.constants import AcquisitionType # noqa: E402
1413

1514
with nidaqmx.Task() as task:
1615
task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
1716
task.timing.cfg_samp_clk_timing(1000.0, sample_mode=AcquisitionType.FINITE, samps_per_chan=50)
1817

19-
reader = AnalogSingleChannelReader(task.in_stream)
20-
waveform = reader.read_waveform(READ_ALL_AVAILABLE)
18+
waveform = task.read_waveform()
2119
print(f"Acquired data: {waveform.scaled_data}")
2220
print(f"Channel name: {waveform.channel_name}")
2321
print(f"Unit description: {waveform.unit_description}")

generated/nidaqmx/stream_readers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ def read_waveform(
303303
number_of_samples_per_channel))
304304

305305
if waveform is None:
306-
waveform = AnalogWaveform(raw_data=numpy.zeros(number_of_samples_per_channel, dtype=numpy.float64))
306+
waveform = AnalogWaveform(number_of_samples_per_channel)
307307
elif number_of_samples_per_channel > waveform.sample_count:
308308
# TODO: AB#3228924 - if allowed by the caller, increase the sample count of the waveform
309309
raise DaqError(
@@ -519,7 +519,7 @@ def read_waveforms(
519519

520520
if waveforms is None:
521521
waveforms = [
522-
AnalogWaveform(raw_data=numpy.zeros(number_of_samples_per_channel, dtype=numpy.float64))
522+
AnalogWaveform(number_of_samples_per_channel)
523523
for _ in range(number_of_channels)
524524
]
525525
else:

generated/nidaqmx/task/_task.py

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
import threading
44
import warnings
55
from enum import Enum
6-
from typing import List, Tuple, Union
76

87
import numpy
8+
from nitypes.waveform import AnalogWaveform
99
from nidaqmx import utils
10+
from nidaqmx._feature_toggles import WAVEFORM_SUPPORT, requires_feature
1011
from nidaqmx.task.channels._channel import Channel
1112
from nidaqmx.task._export_signals import ExportSignals
1213
from nidaqmx.task._in_stream import InStream
@@ -516,6 +517,7 @@ def read(self, number_of_samples_per_channel=NUM_SAMPLES_UNSET,
516517
517518
This read method is dynamic, and is capable of inferring an appropriate
518519
return type based on these factors:
520+
519521
- The channel type of the task.
520522
- The number of channels to read.
521523
- The number of samples per channel.
@@ -768,6 +770,117 @@ def _read_power(
768770
for v, i in zip(voltages, currents)
769771
][:samples_read]
770772

773+
@requires_feature(WAVEFORM_SUPPORT)
774+
def read_waveform(self, number_of_samples_per_channel=READ_ALL_AVAILABLE,
775+
timeout=10.0):
776+
"""
777+
Reads samples from the task or virtual channels you specify, and returns them as waveforms.
778+
779+
This read method is dynamic, and is capable of inferring an appropriate
780+
return type based on these factors:
781+
782+
- The channel type of the task.
783+
- The number of channels to read.
784+
- The number of samples per channel.
785+
786+
The data type of the samples returned is independently determined by
787+
the channel type of the task.
788+
789+
If you do not set the number of samples per channel, this method
790+
reads all available data for each channel.
791+
792+
Args:
793+
number_of_samples_per_channel (Optional[int]): Specifies the
794+
number of samples to read. If this input is not set,
795+
it defaults to nidaqmx.constants.READ_ALL_AVAILABLE.
796+
797+
If this input is nidaqmx.constants.READ_ALL_AVAILABLE,
798+
NI-DAQmx determines how many samples
799+
to read based on if the task acquires samples
800+
continuously or acquires a finite number of samples.
801+
802+
If the task acquires samples continuously and you set
803+
this input to nidaqmx.constants.READ_ALL_AVAILABLE, this
804+
method reads all the samples currently available in the
805+
buffer.
806+
807+
If the task acquires a finite number of samples and you
808+
set this input to nidaqmx.constants.READ_ALL_AVAILABLE,
809+
the method waits for the task to acquire all requested
810+
samples, then reads those samples. If you set the
811+
"read_all_avail_samp" property to True, the method reads
812+
the samples currently available in the buffer and does
813+
not wait for the task to acquire all requested samples.
814+
timeout (Optional[float]): Specifies the amount of time in
815+
seconds to wait for samples to become available. If the
816+
time elapses, the method returns an error and any
817+
samples read before the timeout elapsed. The default
818+
timeout is 10 seconds. If you set timeout to
819+
nidaqmx.constants.WAIT_INFINITELY, the method waits
820+
indefinitely. If you set timeout to 0, the method tries
821+
once to read the requested samples and returns an error
822+
if it is unable to.
823+
Returns:
824+
dynamic:
825+
826+
The samples requested in the form of a waveform (for a single channel)
827+
or a list of waveforms (for multiple channels).
828+
See method docstring for more info.
829+
830+
NI-DAQmx scales the data to the units of the measurement,
831+
including any custom scaling you apply to the channels. Use a
832+
DAQmx Create Channel method to specify these units.
833+
834+
Example:
835+
>>> task = Task()
836+
>>> task.ai_channels.add_ai_voltage_chan('Dev1/ai0')
837+
>>> data = task.read_waveform()
838+
>>> type(data)
839+
<type 'AnalogWaveform'>
840+
"""
841+
channels_to_read = self.in_stream.channels_to_read
842+
number_of_channels = len(channels_to_read.channel_names)
843+
read_chan_type = channels_to_read.chan_type
844+
845+
number_of_samples_per_channel = self._calculate_num_samps_per_chan(
846+
number_of_samples_per_channel)
847+
848+
if read_chan_type == ChannelType.ANALOG_INPUT:
849+
if number_of_channels == 1:
850+
waveform = AnalogWaveform(number_of_samples_per_channel)
851+
self._interpreter.read_analog_waveform(
852+
self._handle,
853+
number_of_samples_per_channel,
854+
timeout,
855+
waveform,
856+
self._in_stream.waveform_attribute_mode,
857+
)
858+
return waveform
859+
else:
860+
waveforms = [
861+
AnalogWaveform(number_of_samples_per_channel)
862+
for _ in range(number_of_channels)
863+
]
864+
self._interpreter.read_analog_waveforms(
865+
self._handle,
866+
number_of_samples_per_channel,
867+
timeout,
868+
waveforms,
869+
self._in_stream.waveform_attribute_mode,
870+
)
871+
return waveforms
872+
873+
elif (read_chan_type == ChannelType.DIGITAL_INPUT or
874+
read_chan_type == ChannelType.DIGITAL_OUTPUT):
875+
raise NotImplementedError("Digital input/output reading is not implemented yet.")
876+
877+
else:
878+
raise DaqError(
879+
'Read failed, because there are no channels in this task from '
880+
'which data can be read.',
881+
DAQmxErrors.READ_NO_INPUT_CHANS_IN_TASK,
882+
task_name=self.name)
883+
771884
def register_done_event(self, callback_method):
772885
"""
773886
Registers a callback function to receive an event when a task stops due

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ reportArgumentType = false
186186
reportAttributeAccessIssue = false
187187
reportInvalidTypeForm = false
188188
reportOperatorIssue = false
189+
reportOptionalIterable = false
189190
reportOptionalMemberAccess = false
190191
reportReturnType = false
191192

src/handwritten/stream_readers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ def read_waveform(
303303
number_of_samples_per_channel))
304304

305305
if waveform is None:
306-
waveform = AnalogWaveform(raw_data=numpy.zeros(number_of_samples_per_channel, dtype=numpy.float64))
306+
waveform = AnalogWaveform(number_of_samples_per_channel)
307307
elif number_of_samples_per_channel > waveform.sample_count:
308308
# TODO: AB#3228924 - if allowed by the caller, increase the sample count of the waveform
309309
raise DaqError(
@@ -519,7 +519,7 @@ def read_waveforms(
519519

520520
if waveforms is None:
521521
waveforms = [
522-
AnalogWaveform(raw_data=numpy.zeros(number_of_samples_per_channel, dtype=numpy.float64))
522+
AnalogWaveform(number_of_samples_per_channel)
523523
for _ in range(number_of_channels)
524524
]
525525
else:

src/handwritten/task/_task.py

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
import threading
44
import warnings
55
from enum import Enum
6-
from typing import List, Tuple, Union
76

87
import numpy
8+
from nitypes.waveform import AnalogWaveform
99
from nidaqmx import utils
10+
from nidaqmx._feature_toggles import WAVEFORM_SUPPORT, requires_feature
1011
from nidaqmx.task.channels._channel import Channel
1112
from nidaqmx.task._export_signals import ExportSignals
1213
from nidaqmx.task._in_stream import InStream
@@ -516,6 +517,7 @@ def read(self, number_of_samples_per_channel=NUM_SAMPLES_UNSET,
516517
517518
This read method is dynamic, and is capable of inferring an appropriate
518519
return type based on these factors:
520+
519521
- The channel type of the task.
520522
- The number of channels to read.
521523
- The number of samples per channel.
@@ -768,6 +770,117 @@ def _read_power(
768770
for v, i in zip(voltages, currents)
769771
][:samples_read]
770772

773+
@requires_feature(WAVEFORM_SUPPORT)
774+
def read_waveform(self, number_of_samples_per_channel=READ_ALL_AVAILABLE,
775+
timeout=10.0):
776+
"""
777+
Reads samples from the task or virtual channels you specify, and returns them as waveforms.
778+
779+
This read method is dynamic, and is capable of inferring an appropriate
780+
return type based on these factors:
781+
782+
- The channel type of the task.
783+
- The number of channels to read.
784+
- The number of samples per channel.
785+
786+
The data type of the samples returned is independently determined by
787+
the channel type of the task.
788+
789+
If you do not set the number of samples per channel, this method
790+
reads all available data for each channel.
791+
792+
Args:
793+
number_of_samples_per_channel (Optional[int]): Specifies the
794+
number of samples to read. If this input is not set,
795+
it defaults to nidaqmx.constants.READ_ALL_AVAILABLE.
796+
797+
If this input is nidaqmx.constants.READ_ALL_AVAILABLE,
798+
NI-DAQmx determines how many samples
799+
to read based on if the task acquires samples
800+
continuously or acquires a finite number of samples.
801+
802+
If the task acquires samples continuously and you set
803+
this input to nidaqmx.constants.READ_ALL_AVAILABLE, this
804+
method reads all the samples currently available in the
805+
buffer.
806+
807+
If the task acquires a finite number of samples and you
808+
set this input to nidaqmx.constants.READ_ALL_AVAILABLE,
809+
the method waits for the task to acquire all requested
810+
samples, then reads those samples. If you set the
811+
"read_all_avail_samp" property to True, the method reads
812+
the samples currently available in the buffer and does
813+
not wait for the task to acquire all requested samples.
814+
timeout (Optional[float]): Specifies the amount of time in
815+
seconds to wait for samples to become available. If the
816+
time elapses, the method returns an error and any
817+
samples read before the timeout elapsed. The default
818+
timeout is 10 seconds. If you set timeout to
819+
nidaqmx.constants.WAIT_INFINITELY, the method waits
820+
indefinitely. If you set timeout to 0, the method tries
821+
once to read the requested samples and returns an error
822+
if it is unable to.
823+
Returns:
824+
dynamic:
825+
826+
The samples requested in the form of a waveform (for a single channel)
827+
or a list of waveforms (for multiple channels).
828+
See method docstring for more info.
829+
830+
NI-DAQmx scales the data to the units of the measurement,
831+
including any custom scaling you apply to the channels. Use a
832+
DAQmx Create Channel method to specify these units.
833+
834+
Example:
835+
>>> task = Task()
836+
>>> task.ai_channels.add_ai_voltage_chan('Dev1/ai0')
837+
>>> data = task.read_waveform()
838+
>>> type(data)
839+
<type 'AnalogWaveform'>
840+
"""
841+
channels_to_read = self.in_stream.channels_to_read
842+
number_of_channels = len(channels_to_read.channel_names)
843+
read_chan_type = channels_to_read.chan_type
844+
845+
number_of_samples_per_channel = self._calculate_num_samps_per_chan(
846+
number_of_samples_per_channel)
847+
848+
if read_chan_type == ChannelType.ANALOG_INPUT:
849+
if number_of_channels == 1:
850+
waveform = AnalogWaveform(number_of_samples_per_channel)
851+
self._interpreter.read_analog_waveform(
852+
self._handle,
853+
number_of_samples_per_channel,
854+
timeout,
855+
waveform,
856+
self._in_stream.waveform_attribute_mode,
857+
)
858+
return waveform
859+
else:
860+
waveforms = [
861+
AnalogWaveform(number_of_samples_per_channel)
862+
for _ in range(number_of_channels)
863+
]
864+
self._interpreter.read_analog_waveforms(
865+
self._handle,
866+
number_of_samples_per_channel,
867+
timeout,
868+
waveforms,
869+
self._in_stream.waveform_attribute_mode,
870+
)
871+
return waveforms
872+
873+
elif (read_chan_type == ChannelType.DIGITAL_INPUT or
874+
read_chan_type == ChannelType.DIGITAL_OUTPUT):
875+
raise NotImplementedError("Digital input/output reading is not implemented yet.")
876+
877+
else:
878+
raise DaqError(
879+
'Read failed, because there are no channels in this task from '
880+
'which data can be read.',
881+
DAQmxErrors.READ_NO_INPUT_CHANS_IN_TASK,
882+
task_name=self.name)
883+
771884
def register_done_event(self, callback_method):
772885
"""
773886
Registers a callback function to receive an event when a task stops due

0 commit comments

Comments
 (0)