Skip to content

Commit 43fad37

Browse files
mikeprosserniMike Prosser
andauthored
Handle reads that don't match Waveform length (#819)
* handle short reads * ReallocationPolicy * use GROUP_BY_SCAN_NUMBER instead of reshaping read_array * change ReallocationPolicy to a parameter * cleanup * compare against capacity instead of sample_count when using ReallocationPolicy.TO_GROW * cleanup * remove dead code * ReallocationPolicy should default to TO_GROW * if waveform._start_index + number_of_samples_per_channel > waveform.capacity: * clean up setting of waveform.sample_count --------- Co-authored-by: Mike Prosser <Mike.Prosser@emerson.com>
1 parent faf34c9 commit 43fad37

22 files changed

+500
-186
lines changed

generated/nidaqmx/_library_interpreter.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6392,6 +6392,8 @@ def read_analog_waveform(
63926392
t0_array = None
63936393
dt_array = None
63946394

6395+
waveform.sample_count = number_of_samples_per_channel
6396+
63956397
error_code, samples_read = self._internal_read_analog_waveform_ex(
63966398
task_handle,
63976399
number_of_samples_per_channel,
@@ -6403,10 +6405,11 @@ def read_analog_waveform(
64036405
dt_array,
64046406
)
64056407

6408+
waveform.sample_count = samples_read
6409+
64066410
if t0_array is not None and dt_array is not None:
64076411
self._set_waveform_timings([waveform], t0_array, dt_array)
64086412

6409-
# TODO: AB#3228924 - if the read was short, set waveform.sample_count before throwing the exception
64106413
self.check_for_error(error_code, samps_per_chan_read=samples_read)
64116414
return samples_read
64126415

@@ -6431,6 +6434,9 @@ def read_analog_waveforms(
64316434
t0_array = None
64326435
dt_array = None
64336436

6437+
for waveform in waveforms:
6438+
waveform.sample_count = number_of_samples_per_channel
6439+
64346440
error_code, samples_read = self._internal_read_analog_waveform_per_chan(
64356441
task_handle,
64366442
number_of_samples_per_channel,
@@ -6441,10 +6447,12 @@ def read_analog_waveforms(
64416447
dt_array,
64426448
)
64436449

6450+
for waveform in waveforms:
6451+
waveform.sample_count = samples_read
6452+
64446453
if t0_array is not None and dt_array is not None:
64456454
self._set_waveform_timings(waveforms, t0_array, dt_array)
64466455

6447-
# TODO: AB#3228924 - if the read was short, set waveform.sample_count before throwing the exception
64486456
self.check_for_error(error_code, samps_per_chan_read=samples_read)
64496457
return samples_read
64506458

@@ -6660,6 +6668,8 @@ def read_digital_waveform(
66606668
t0_array = None
66616669
dt_array = None
66626670

6671+
waveform.sample_count = number_of_samples_per_channel
6672+
66636673
error_code, samples_read = self._internal_read_digital_waveform(
66646674
task_handle,
66656675
number_of_samples_per_channel,
@@ -6672,10 +6682,11 @@ def read_digital_waveform(
66726682
None,
66736683
)
66746684

6685+
waveform.sample_count = samples_read
6686+
66756687
if t0_array is not None and dt_array is not None:
66766688
self._set_waveform_timings([waveform], t0_array, dt_array)
66776689

6678-
# TODO: AB#3228924 - if the read was short, set waveform.sample_count before throwing the exception
66796690
self.check_for_error(error_code, samps_per_chan_read=samples_read)
66806691
return samples_read
66816692

@@ -6705,7 +6716,7 @@ def read_digital_waveforms(
67056716
# Since there's no DAQmxInternalReadDigitalWaveformPerChan, we have to allocate a
67066717
# temporary contiguous array to read the data from multiple channels into.
67076718
read_array = numpy.zeros(
6708-
(channel_count, number_of_samples_per_channel, number_of_signals_per_sample),
6719+
(number_of_samples_per_channel, channel_count, number_of_signals_per_sample),
67096720
dtype=numpy.uint8)
67106721

67116722
bytes_per_chan_array = numpy.zeros(channel_count, dtype=numpy.uint32)
@@ -6714,25 +6725,25 @@ def read_digital_waveforms(
67146725
task_handle,
67156726
number_of_samples_per_channel,
67166727
timeout,
6717-
FillMode.GROUP_BY_CHANNEL.value,
6728+
FillMode.GROUP_BY_SCAN_NUMBER.value, # GROUP_BY_SCAN_NUMBER handles short reads better than GROUP_BY_CHANNEL
67186729
read_array,
67196730
properties,
67206731
t0_array,
67216732
dt_array,
67226733
bytes_per_chan_array,
67236734
)
67246735

6725-
for i, waveform in enumerate(waveforms):
6726-
waveform_signals = waveform.data.shape[1]
6727-
channel_signals = bytes_per_chan_array[i]
6728-
if waveform_signals != channel_signals:
6729-
raise ValueError(f"waveforms[{i}].data has {waveform_signals} signals, but expected {channel_signals}")
6730-
waveform.data[:] = read_array[i, :, :channel_signals]
6736+
for i, waveform in enumerate(waveforms):
6737+
waveform.sample_count = samples_read
6738+
waveform_signal_count = waveform.data.shape[1]
6739+
channel_signal_count = bytes_per_chan_array[i]
6740+
if waveform_signal_count != channel_signal_count:
6741+
raise ValueError(f"waveforms[{i}].data has {waveform_signal_count} signals, but expected {channel_signal_count}")
6742+
waveform.data[:] = read_array[:, i, :channel_signal_count]
67316743

67326744
if t0_array is not None and dt_array is not None:
67336745
self._set_waveform_timings(waveforms, t0_array, dt_array)
67346746

6735-
# TODO: AB#3228924 - if the read was short, set waveform.sample_count before throwing the exception
67366747
self.check_for_error(error_code, samps_per_chan_read=samples_read)
67376748
return samples_read
67386749

@@ -6759,7 +6770,7 @@ def read_new_digital_waveforms(
67596770
dt_array = None
67606771

67616772
read_array = numpy.zeros(
6762-
(channel_count, number_of_samples_per_channel, number_of_signals_per_sample),
6773+
(number_of_samples_per_channel, channel_count, number_of_signals_per_sample),
67636774
dtype=numpy.uint8)
67646775

67656776
bytes_per_chan_array = numpy.zeros(channel_count, dtype=numpy.uint32)
@@ -6768,7 +6779,7 @@ def read_new_digital_waveforms(
67686779
task_handle,
67696780
number_of_samples_per_channel,
67706781
timeout,
6771-
FillMode.GROUP_BY_CHANNEL.value,
6782+
FillMode.GROUP_BY_SCAN_NUMBER.value, # GROUP_BY_SCAN_NUMBER handles short reads better than GROUP_BY_CHANNEL
67726783
read_array,
67736784
properties,
67746785
t0_array,
@@ -6778,10 +6789,10 @@ def read_new_digital_waveforms(
67786789

67796790
waveforms = []
67806791
for i in range(channel_count):
6781-
signal_count = bytes_per_chan_array[i]
6792+
channel_signal_count = bytes_per_chan_array[i]
67826793
waveform = DigitalWaveform(
67836794
sample_count=samples_read,
6784-
data=read_array[i, :, :signal_count],
6795+
data=read_array[:, i, :channel_signal_count],
67856796
copy_extended_properties=False,
67866797
extended_properties=properties[i] if properties else None)
67876798
waveforms.append(waveform)

generated/nidaqmx/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,11 @@ class _TriggerUsageTypes(Enum):
10261026
ARM_START = 32 #: Device supports arm start triggers
10271027

10281028

1029+
class ReallocationPolicy(Enum):
1030+
DO_NOT_REALLOCATE = 0 #: Do not reallocate waveforms.
1031+
TO_GROW = 1 #: Reallocate waveforms to grow when needed.
1032+
1033+
10291034
class WaveformAttributeMode(Flag):
10301035
NONE = 0
10311036
TIMING = 1

generated/nidaqmx/stream_readers.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

generated/nidaqmx/stream_readers/_analog_multi_channel_reader.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from nidaqmx import DaqError
55

66
from nidaqmx._feature_toggles import WAVEFORM_SUPPORT, requires_feature
7-
from nidaqmx.constants import FillMode, READ_ALL_AVAILABLE
7+
from nidaqmx.constants import FillMode, READ_ALL_AVAILABLE, ReallocationPolicy
88
from nidaqmx.error_codes import DAQmxErrors
99
from nitypes.waveform import AnalogWaveform
1010

@@ -143,6 +143,7 @@ def read_waveforms(
143143
self,
144144
waveforms: list[AnalogWaveform[numpy.float64]],
145145
number_of_samples_per_channel: int = READ_ALL_AVAILABLE,
146+
reallocation_policy: ReallocationPolicy = ReallocationPolicy.TO_GROW,
146147
timeout: int = 10,
147148
) -> int:
148149
"""
@@ -181,6 +182,9 @@ def read_waveforms(
181182
"read_all_avail_samp" property to True, the method reads
182183
the samples currently available in the buffer and does
183184
not wait for the task to acquire all requested samples.
185+
reallocation_policy (Optional[ReallocationPolicy]): Specifies
186+
the reallocation policy to use when the read yields more
187+
samples than the current capacity of the waveform.
184188
timeout (Optional[float]): Specifies the amount of time in
185189
seconds to wait for samples to become available. If the
186190
time elapses, the method returns an error and any
@@ -210,13 +214,15 @@ def read_waveforms(
210214
DAQmxErrors.MISMATCHED_INPUT_ARRAY_SIZES, task_name=self._task.name)
211215

212216
for i, waveform in enumerate(waveforms):
213-
if number_of_samples_per_channel > waveform.sample_count:
214-
# TODO: AB#3228924 - if allowed by the caller, increase the sample count of the waveform
215-
raise DaqError(
216-
f'The waveform at index {i} does not have enough space ({waveform.sample_count}) to hold '
217-
f'the requested number of samples ({number_of_samples_per_channel}). Please provide larger '
218-
'waveforms or adjust the number of samples requested.',
219-
DAQmxErrors.READ_BUFFER_TOO_SMALL, task_name=self._task.name)
217+
if waveform._start_index + number_of_samples_per_channel > waveform.capacity:
218+
if reallocation_policy == ReallocationPolicy.TO_GROW:
219+
waveform.capacity = waveform._start_index + number_of_samples_per_channel
220+
else:
221+
raise DaqError(
222+
f'The waveform at index {i} does not have enough space ({waveform.capacity - waveform._start_index}) to hold '
223+
f'the requested number of samples ({number_of_samples_per_channel}). Please provide larger '
224+
'waveforms or adjust the number of samples requested.',
225+
DAQmxErrors.READ_BUFFER_TOO_SMALL, task_name=self._task.name)
220226

221227
return self._interpreter.read_analog_waveforms(
222228
self._handle,

generated/nidaqmx/stream_readers/_analog_single_channel_reader.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from nidaqmx import DaqError
55

66
from nidaqmx._feature_toggles import WAVEFORM_SUPPORT, requires_feature
7-
from nidaqmx.constants import FillMode, READ_ALL_AVAILABLE
7+
from nidaqmx.constants import FillMode, READ_ALL_AVAILABLE, ReallocationPolicy
88
from nidaqmx.error_codes import DAQmxErrors
99
from nitypes.waveform import AnalogWaveform
1010

@@ -115,6 +115,7 @@ def read_waveform(
115115
self,
116116
waveform: AnalogWaveform[numpy.float64],
117117
number_of_samples_per_channel: int = READ_ALL_AVAILABLE,
118+
reallocation_policy: ReallocationPolicy = ReallocationPolicy.TO_GROW,
118119
timeout: int = 10,
119120
) -> int:
120121
"""
@@ -152,6 +153,9 @@ def read_waveform(
152153
"read_all_avail_samp" property to True, the method reads
153154
the samples currently available in the buffer and does
154155
not wait for the task to acquire all requested samples.
156+
reallocation_policy (Optional[ReallocationPolicy]): Specifies
157+
the reallocation policy to use when the read yields more
158+
samples than the current capacity of the waveform.
155159
timeout (Optional[float]): Specifies the amount of time in
156160
seconds to wait for samples to become available. If the
157161
time elapses, the method returns an error and any
@@ -170,12 +174,14 @@ def read_waveform(
170174
self._task._calculate_num_samps_per_chan(
171175
number_of_samples_per_channel))
172176

173-
if number_of_samples_per_channel > waveform.sample_count:
174-
# TODO: AB#3228924 - if allowed by the caller, increase the sample count of the waveform
175-
raise DaqError(
176-
f'The provided waveform does not have enough space ({waveform.sample_count}) to hold '
177-
f'the requested number of samples ({number_of_samples_per_channel}). Please provide a larger '
178-
'waveform or adjust the number of samples requested.',
177+
if waveform._start_index + number_of_samples_per_channel > waveform.capacity:
178+
if reallocation_policy == ReallocationPolicy.TO_GROW:
179+
waveform.capacity = waveform._start_index + number_of_samples_per_channel
180+
else:
181+
raise DaqError(
182+
f'The provided waveform does not have enough space ({waveform.capacity - waveform._start_index}) to hold '
183+
f'the requested number of samples ({number_of_samples_per_channel}). Please provide a larger '
184+
'waveform or adjust the number of samples requested.',
179185
DAQmxErrors.READ_BUFFER_TOO_SMALL, task_name=self._task.name)
180186

181187
return self._interpreter.read_analog_waveform(

generated/nidaqmx/stream_readers/_digital_multi_channel_reader.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from nidaqmx import DaqError
55

66
from nidaqmx._feature_toggles import WAVEFORM_SUPPORT, requires_feature
7-
from nidaqmx.constants import FillMode, READ_ALL_AVAILABLE
7+
from nidaqmx.constants import FillMode, READ_ALL_AVAILABLE, ReallocationPolicy
88
from nidaqmx.error_codes import DAQmxErrors
99
from nitypes.waveform import DigitalWaveform
1010

@@ -487,6 +487,7 @@ def read_waveforms(
487487
self,
488488
waveforms: list[DigitalWaveform[numpy.uint8]],
489489
number_of_samples_per_channel: int = READ_ALL_AVAILABLE,
490+
reallocation_policy: ReallocationPolicy = ReallocationPolicy.TO_GROW,
490491
timeout: int = 10,
491492
) -> list[DigitalWaveform[numpy.uint8]]:
492493
"""
@@ -518,6 +519,9 @@ def read_waveforms(
518519
"read_all_avail_samp" property to True, the method reads
519520
the samples currently available in the buffer and does
520521
not wait for the task to acquire all requested samples.
522+
reallocation_policy (Optional[ReallocationPolicy]): Specifies
523+
the reallocation policy to use when the read yields more
524+
samples than the current capacity of the waveform.
521525
timeout (Optional[float]): Specifies the amount of time in
522526
seconds to wait for samples to become available. If the
523527
time elapses, the method returns an error and any
@@ -547,13 +551,15 @@ def read_waveforms(
547551
DAQmxErrors.MISMATCHED_INPUT_ARRAY_SIZES, task_name=self._task.name)
548552

549553
for i, waveform in enumerate(waveforms):
550-
if number_of_samples_per_channel > waveform.sample_count:
551-
# TODO: AB#3228924 - if allowed by the caller, increase the sample count of the waveform
552-
raise DaqError(
553-
f'The waveform at index {i} does not have enough space ({waveform.sample_count}) to hold '
554-
f'the requested number of samples ({number_of_samples_per_channel}). Please provide larger '
555-
'waveforms or adjust the number of samples requested.',
556-
DAQmxErrors.READ_BUFFER_TOO_SMALL, task_name=self._task.name)
554+
if waveform._start_index + number_of_samples_per_channel > waveform.capacity:
555+
if reallocation_policy == ReallocationPolicy.TO_GROW:
556+
waveform.capacity = waveform._start_index + number_of_samples_per_channel
557+
else:
558+
raise DaqError(
559+
f'The waveform at index {i} does not have enough space ({waveform.capacity - waveform._start_index}) to hold '
560+
f'the requested number of samples ({number_of_samples_per_channel}). Please provide larger '
561+
'waveforms or adjust the number of samples requested.',
562+
DAQmxErrors.READ_BUFFER_TOO_SMALL, task_name=self._task.name)
557563

558564
waveforms = self._interpreter.read_digital_waveforms(
559565
self._handle,

generated/nidaqmx/stream_readers/_digital_single_channel_reader.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from __future__ import annotations
22

33
import numpy
4+
from nidaqmx import DaqError
45

56
from nidaqmx._feature_toggles import WAVEFORM_SUPPORT, requires_feature
6-
from nidaqmx.constants import FillMode, READ_ALL_AVAILABLE
7+
from nidaqmx.constants import FillMode, READ_ALL_AVAILABLE, ReallocationPolicy
8+
from nidaqmx.error_codes import DAQmxErrors
79
from nitypes.waveform import DigitalWaveform
810

911
from nidaqmx.stream_readers._channel_reader_base import ChannelReaderBase
@@ -388,6 +390,7 @@ def read_waveform(
388390
self,
389391
waveform: DigitalWaveform[numpy.uint8],
390392
number_of_samples_per_channel: int = READ_ALL_AVAILABLE,
393+
reallocation_policy: ReallocationPolicy = ReallocationPolicy.TO_GROW,
391394
timeout: float = 10.0,
392395
) -> int:
393396
"""
@@ -418,6 +421,9 @@ def read_waveform(
418421
"read_all_avail_samp" property to True, the method reads
419422
the samples currently available in the buffer and does
420423
not wait for the task to acquire all requested samples.
424+
reallocation_policy (Optional[ReallocationPolicy]): Specifies
425+
the reallocation policy to use when the read yields more
426+
samples than the current capacity of the waveform.
421427
timeout (Optional[float]): Specifies the amount of time in
422428
seconds to wait for samples to become available. If the
423429
time elapses, the method returns an error and any
@@ -438,6 +444,16 @@ def read_waveform(
438444
self._task._calculate_num_samps_per_chan(
439445
number_of_samples_per_channel))
440446

447+
if waveform._start_index + number_of_samples_per_channel > waveform.capacity:
448+
if reallocation_policy == ReallocationPolicy.TO_GROW:
449+
waveform.capacity = waveform._start_index + number_of_samples_per_channel
450+
else:
451+
raise DaqError(
452+
f'The waveform does not have enough space ({waveform.capacity - waveform._start_index}) to hold '
453+
f'the requested number of samples ({number_of_samples_per_channel}). Please '
454+
'provide a larger waveform or adjust the number of samples requested.',
455+
DAQmxErrors.READ_BUFFER_TOO_SMALL, task_name=self._task.name)
456+
441457
return self._interpreter.read_digital_waveform(
442458
self._handle,
443459
number_of_samples_per_channel,

generated/nidaqmx/stream_writers.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)