Skip to content

Commit 233188f

Browse files
mikeprosserniMike Prosser
andauthored
Add in_stream.waveform_attribute_mode (#800)
* first draft * cleanup * test cleanup * _internal_read_analog_waveform_ex to take in Nones to avoid allocations * fix merge * implement WaveformAttributeMode in read_analog_waveforms and internal_read_analog_waveform_per_chan * refactor how the arrays for timing information are handled * _get_wfm_attr_callback() helper * cleanup --------- Co-authored-by: Mike Prosser <[email protected]>
1 parent df4fa7f commit 233188f

File tree

13 files changed

+436
-152
lines changed

13 files changed

+436
-152
lines changed

generated/nidaqmx/_base_interpreter.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import numpy
44
from nitypes.waveform import AnalogWaveform
55
from typing import Sequence
6+
from nidaqmx.constants import WaveformAttributeMode
67

78

89
class BaseEventHandler(abc.ABC):
@@ -1855,7 +1856,8 @@ def read_analog_waveform(
18551856
task_handle: object,
18561857
number_of_samples_per_channel: int,
18571858
timeout: float,
1858-
waveform: AnalogWaveform[numpy.float64]
1859+
waveform: AnalogWaveform[numpy.float64],
1860+
waveform_attribute_mode: WaveformAttributeMode
18591861
) -> None:
18601862
raise NotImplementedError
18611863

@@ -1865,6 +1867,7 @@ def read_analog_waveforms(
18651867
task_handle: object,
18661868
number_of_samples_per_channel: int,
18671869
timeout: float,
1868-
waveforms: Sequence[AnalogWaveform[numpy.float64]]
1870+
waveforms: Sequence[AnalogWaveform[numpy.float64]],
1871+
waveform_attribute_mode: WaveformAttributeMode
18691872
) -> None:
18701873
raise NotImplementedError

generated/nidaqmx/_grpc_interpreter.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from nidaqmx._stubs import nidaqmx_pb2 as grpc_types
1919
from nidaqmx._stubs import nidaqmx_pb2_grpc as nidaqmx_grpc
2020
from nidaqmx._stubs import session_pb2 as session_grpc_types
21+
from nidaqmx.constants import WaveformAttributeMode
2122
from nidaqmx.error_codes import DAQmxErrors
2223
from nidaqmx._grpc_time import convert_time_to_timestamp, convert_timestamp_to_time
2324

@@ -3606,7 +3607,8 @@ def read_analog_waveform(
36063607
task_handle: object,
36073608
number_of_samples_per_channel: int,
36083609
timeout: float,
3609-
waveform: AnalogWaveform[numpy.float64]
3610+
waveform: AnalogWaveform[numpy.float64],
3611+
waveform_attribute_mode: WaveformAttributeMode
36103612
) -> None:
36113613
raise NotImplementedError
36123614

@@ -3615,7 +3617,8 @@ def read_analog_waveforms(
36153617
task_handle: object,
36163618
number_of_samples_per_channel: int,
36173619
timeout: float,
3618-
waveforms: Sequence[AnalogWaveform[numpy.float64]]
3620+
waveforms: Sequence[AnalogWaveform[numpy.float64]],
3621+
waveform_attribute_mode: WaveformAttributeMode
36193622
) -> None:
36203623
raise NotImplementedError
36213624

generated/nidaqmx/_library_interpreter.py

Lines changed: 81 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
from datetime import timezone
1212
from hightime import datetime as ht_datetime
1313
from hightime import timedelta as ht_timedelta
14-
from typing import Callable, List, Optional, Sequence, Tuple, TYPE_CHECKING
14+
from typing import Callable, List, MutableSequence, Optional, Sequence, Tuple, TYPE_CHECKING
1515

1616
from nidaqmx._base_interpreter import BaseEventHandler, BaseInterpreter
1717
from nidaqmx._lib import lib_importer, ctypes_byte_str, c_bool32, wrapped_ndpointer, TaskHandle
18-
from nidaqmx.constants import FillMode
18+
from nidaqmx.constants import FillMode, WaveformAttributeMode
1919
from nidaqmx.error_codes import DAQmxErrors, DAQmxWarnings
2020
from nidaqmx.errors import DaqError, DaqFunctionNotSupportedError, DaqReadError, DaqWarning, DaqWriteError
2121
from nidaqmx._lib_time import AbsoluteTime
@@ -6376,24 +6376,35 @@ def read_analog_waveform(
63766376
task_handle: object,
63776377
number_of_samples_per_channel: int,
63786378
timeout: float,
6379-
waveform: AnalogWaveform[numpy.float64]
6379+
waveform: AnalogWaveform[numpy.float64],
6380+
waveform_attribute_mode: WaveformAttributeMode
63806381
) -> None:
63816382
"""Read an analog waveform with timing and attributes."""
6382-
error_code, samples_read, timestamps, sample_intervals = self._internal_read_analog_waveform_ex(
6383+
if WaveformAttributeMode.EXTENDED_PROPERTIES in waveform_attribute_mode:
6384+
properties = [waveform.extended_properties]
6385+
else:
6386+
properties = None
6387+
6388+
if WaveformAttributeMode.TIMING in waveform_attribute_mode:
6389+
t0_array = numpy.zeros(1, dtype=numpy.int64)
6390+
dt_array = numpy.zeros(1, dtype=numpy.int64)
6391+
else:
6392+
t0_array = None
6393+
dt_array = None
6394+
6395+
error_code, samples_read = self._internal_read_analog_waveform_ex(
63836396
task_handle,
6384-
1, # single channel
63856397
number_of_samples_per_channel,
63866398
timeout,
63876399
FillMode.GROUP_BY_CHANNEL.value,
63886400
waveform.raw_data,
6389-
[waveform.extended_properties]
6401+
properties,
6402+
t0_array,
6403+
dt_array,
63906404
)
63916405

6392-
waveform.timing = Timing(
6393-
sample_interval_mode=SampleIntervalMode.REGULAR,
6394-
timestamp=timestamps[0],
6395-
sample_interval=sample_intervals[0],
6396-
)
6406+
if t0_array is not None and dt_array is not None:
6407+
self._set_waveform_timings([waveform], t0_array, dt_array)
63976408

63986409
# TODO: AB#3228924 - if the read was short, set waveform.sample_count before throwing the exception
63996410
self.check_for_error(error_code, samps_per_chan_read=samples_read)
@@ -6403,41 +6414,51 @@ def read_analog_waveforms(
64036414
task_handle: object,
64046415
number_of_samples_per_channel: int,
64056416
timeout: float,
6406-
waveforms: Sequence[AnalogWaveform[numpy.float64]]
6417+
waveforms: Sequence[AnalogWaveform[numpy.float64]],
6418+
waveform_attribute_mode: WaveformAttributeMode
64076419
) -> None:
64086420
"""Read a set of analog waveforms with timing and attributes. All of the waveforms must be the same size."""
6409-
error_code, samples_read, timestamps, sample_intervals = self.internal_read_analog_waveform_per_chan(
6421+
if WaveformAttributeMode.EXTENDED_PROPERTIES in waveform_attribute_mode:
6422+
properties = [waveform.extended_properties for waveform in waveforms]
6423+
else:
6424+
properties = None
6425+
6426+
if WaveformAttributeMode.TIMING in waveform_attribute_mode:
6427+
t0_array = numpy.zeros(len(waveforms), dtype=numpy.int64)
6428+
dt_array = numpy.zeros(len(waveforms), dtype=numpy.int64)
6429+
else:
6430+
t0_array = None
6431+
dt_array = None
6432+
6433+
error_code, samples_read = self.internal_read_analog_waveform_per_chan(
64106434
task_handle,
64116435
number_of_samples_per_channel,
64126436
timeout,
64136437
[waveform.raw_data for waveform in waveforms],
6414-
[waveform.extended_properties for waveform in waveforms]
6438+
properties,
6439+
t0_array,
6440+
dt_array,
64156441
)
64166442

6417-
for i, waveform in enumerate(waveforms):
6418-
waveform.timing = Timing(
6419-
sample_interval_mode=SampleIntervalMode.REGULAR,
6420-
timestamp=timestamps[i],
6421-
sample_interval=sample_intervals[i],
6422-
)
6443+
if t0_array is not None and dt_array is not None:
6444+
self._set_waveform_timings(waveforms, t0_array, dt_array)
64236445

64246446
# TODO: AB#3228924 - if the read was short, set waveform.sample_count before throwing the exception
64256447
self.check_for_error(error_code, samps_per_chan_read=samples_read)
64266448

64276449
def _internal_read_analog_waveform_ex(
64286450
self,
64296451
task_handle: object,
6430-
channel_count: int,
64316452
number_of_samples_per_channel: int,
64326453
timeout: float,
64336454
fill_mode: int,
64346455
read_array: numpy.typing.NDArray[numpy.float64],
6435-
properties: Sequence[ExtendedPropertyDictionary]
6456+
properties: Sequence[ExtendedPropertyDictionary] | None,
6457+
t0_array: numpy.typing.NDArray[numpy.int64] | None,
6458+
dt_array: numpy.typing.NDArray[numpy.int64] | None,
64366459
) -> Tuple[
64376460
int, # error code
64386461
int, # The number of samples per channel that were read
6439-
Sequence[ht_datetime], # The timestamps for each sample, indexed by channel
6440-
Sequence[ht_timedelta], # The sample intervals, indexed by channel
64416462
]:
64426463
assert isinstance(task_handle, TaskHandle)
64436464
samps_per_chan_read = ctypes.c_int()
@@ -6462,19 +6483,6 @@ def _internal_read_analog_waveform_ex(
64626483
ctypes.POINTER(c_bool32),
64636484
]
64646485

6465-
t0_array = numpy.zeros(channel_count, dtype=numpy.int64)
6466-
dt_array = numpy.zeros(channel_count, dtype=numpy.int64)
6467-
6468-
def set_wfm_attr_callback(
6469-
channel_index: int,
6470-
attribute_name: str,
6471-
attribute_type: WfmAttrType,
6472-
value: ExtendedPropertyValue,
6473-
callback_data: object,
6474-
) -> int:
6475-
properties[channel_index][attribute_name] = value
6476-
return 0
6477-
64786486
error_code = cfunc(
64796487
task_handle,
64806488
number_of_samples_per_channel,
@@ -6483,31 +6491,28 @@ def set_wfm_attr_callback(
64836491
t0_array,
64846492
dt_array,
64856493
0 if t0_array is None else t0_array.size,
6486-
self._get_wfm_attr_callback_ptr(set_wfm_attr_callback),
6494+
self._get_wfm_attr_callback(properties),
64876495
None,
64886496
read_array,
64896497
read_array.size,
64906498
ctypes.byref(samps_per_chan_read),
64916499
None,
64926500
)
64936501

6494-
timestamps = [_T0_EPOCH + ht_timedelta(seconds=t0 * _INT64_WFM_SEC_PER_TICK) for t0 in t0_array]
6495-
sample_intervals = [ht_timedelta(seconds=dt * _INT64_WFM_SEC_PER_TICK) for dt in dt_array]
6496-
6497-
return error_code, samps_per_chan_read.value, timestamps, sample_intervals
6502+
return error_code, samps_per_chan_read.value
64986503

64996504
def internal_read_analog_waveform_per_chan(
65006505
self,
65016506
task_handle: object,
65026507
num_samps_per_chan: int,
65036508
timeout: float,
65046509
read_arrays: Sequence[numpy.typing.NDArray[numpy.float64]],
6505-
properties: Sequence[ExtendedPropertyDictionary]
6510+
properties: Sequence[ExtendedPropertyDictionary] | None,
6511+
t0_array: numpy.typing.NDArray[numpy.int64] | None,
6512+
dt_array: numpy.typing.NDArray[numpy.int64] | None,
65066513
) -> Tuple[
65076514
int, # error code
65086515
int, # The number of samples per channel that were read
6509-
Sequence[ht_datetime], # The timestamps for each sample, indexed by channel
6510-
Sequence[ht_timedelta], # The sample intervals, indexed by channel
65116516
]:
65126517
assert isinstance(task_handle, TaskHandle)
65136518
samps_per_chan_read = ctypes.c_int()
@@ -6537,31 +6542,18 @@ def internal_read_analog_waveform_per_chan(
65376542
ctypes.POINTER(c_bool32),
65386543
]
65396544

6540-
t0_array = numpy.zeros(channel_count, dtype=numpy.int64)
6541-
dt_array = numpy.zeros(channel_count, dtype=numpy.int64)
6542-
65436545
read_array_ptrs = (ctypes.POINTER(ctypes.c_double) * channel_count)()
65446546
for i, read_array in enumerate(read_arrays):
65456547
read_array_ptrs[i] = read_array.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
65466548

6547-
def set_wfm_attr_callback(
6548-
channel_index: int,
6549-
attribute_name: str,
6550-
attribute_type: WfmAttrType,
6551-
value: ExtendedPropertyValue,
6552-
callback_data: object,
6553-
) -> int:
6554-
properties[channel_index][attribute_name] = value
6555-
return 0
6556-
65576549
error_code = cfunc(
65586550
task_handle,
65596551
num_samps_per_chan,
65606552
timeout,
65616553
t0_array,
65626554
dt_array,
65636555
0 if t0_array is None else t0_array.size,
6564-
self._get_wfm_attr_callback_ptr(set_wfm_attr_callback),
6556+
self._get_wfm_attr_callback(properties),
65656557
None,
65666558
read_array_ptrs,
65676559
channel_count,
@@ -6571,11 +6563,22 @@ def set_wfm_attr_callback(
65716563
)
65726564
self.check_for_error(error_code, samps_per_chan_read=samps_per_chan_read.value)
65736565

6574-
timestamps = [_T0_EPOCH + ht_timedelta(seconds=t0 * _INT64_WFM_SEC_PER_TICK) for t0 in t0_array]
6575-
sample_intervals = [ht_timedelta(seconds=dt * _INT64_WFM_SEC_PER_TICK) for dt in dt_array]
6576-
6577-
return error_code, samps_per_chan_read.value, timestamps, sample_intervals
6578-
6566+
return error_code, samps_per_chan_read.value
6567+
6568+
def _get_wfm_attr_callback(self, properties):
6569+
if properties is not None:
6570+
def set_wfm_attr_callback(
6571+
channel_index: int,
6572+
attribute_name: str,
6573+
attribute_type: WfmAttrType,
6574+
value: ExtendedPropertyValue,
6575+
callback_data: object,
6576+
) -> int:
6577+
properties[channel_index][attribute_name] = value
6578+
return 0
6579+
return self._get_wfm_attr_callback_ptr(set_wfm_attr_callback)
6580+
else:
6581+
return CSetWfmAttrCallbackPtr()
65796582

65806583
def _get_wfm_attr_value(
65816584
self, attribute_type: int, value: ctypes.c_void_p, value_size_in_bytes: int
@@ -6597,11 +6600,8 @@ def _get_wfm_attr_value(
65976600
raise ValueError(f"Unsupported attribute type {attribute_type}")
65986601

65996602
def _get_wfm_attr_callback_ptr(
6600-
self, set_wfm_attr_callback: Optional[SetWfmAttrCallback]
6603+
self, set_wfm_attr_callback: SetWfmAttrCallback
66016604
) -> ctypes._FuncPointer:
6602-
if set_wfm_attr_callback is None:
6603-
return CSetWfmAttrCallbackPtr()
6604-
66056605
def _invoke_callback(
66066606
channel_index: int,
66076607
attribute_name: bytes,
@@ -6624,6 +6624,19 @@ def _invoke_callback(
66246624

66256625
return CSetWfmAttrCallbackPtr(_invoke_callback)
66266626

6627+
def _set_waveform_timings(
6628+
self,
6629+
waveforms: Sequence[AnalogWaveform[numpy.float64]],
6630+
t0_array: numpy.typing.NDArray[numpy.int64],
6631+
dt_array: numpy.typing.NDArray[numpy.int64]
6632+
) -> None:
6633+
for i, waveform in enumerate(waveforms):
6634+
waveform.timing = Timing(
6635+
sample_interval_mode=SampleIntervalMode.REGULAR,
6636+
timestamp=_T0_EPOCH + ht_timedelta(seconds=t0_array[i] * _INT64_WFM_SEC_PER_TICK),
6637+
sample_interval=ht_timedelta(seconds=dt_array[i] * _INT64_WFM_SEC_PER_TICK),
6638+
)
6639+
66276640
def read_id_pin_memory(self, device_name, id_pin_name):
66286641
data_length_read = ctypes.c_uint()
66296642
format_code = ctypes.c_uint()

generated/nidaqmx/constants.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Do not edit this file; it was automatically generated.
22

3-
from enum import Enum
3+
from enum import Enum, Flag
44

55
# Constants
66
AUTO = -1
@@ -1026,3 +1026,8 @@ class _TriggerUsageTypes(Enum):
10261026
ARM_START = 32 #: Device supports arm start triggers
10271027

10281028

1029+
class WaveformAttributeMode(Flag):
1030+
NONE = 0
1031+
TIMING = 1
1032+
EXTENDED_PROPERTIES = 2
1033+

generated/nidaqmx/stream_readers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ def read_waveform(
317317
number_of_samples_per_channel,
318318
timeout,
319319
waveform,
320+
self._in_stream.waveform_attribute_mode,
320321
)
321322

322323
return waveform
@@ -543,6 +544,7 @@ def read_waveforms(
543544
number_of_samples_per_channel,
544545
timeout,
545546
waveforms,
547+
self._in_stream.waveform_attribute_mode,
546548
)
547549

548550
return waveforms

0 commit comments

Comments
 (0)