diff --git a/poetry.lock b/poetry.lock index abfa4df..6a5d415 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2463,36 +2463,36 @@ protobuf = ">=4.21" [[package]] name = "ni-measurements-data-v1-client" -version = "1.1.0" +version = "1.2.0.dev0" description = "gRPC Client for NI Data Store Service" optional = false python-versions = "<4.0,>=3.10" groups = ["main"] files = [ - {file = "ni_measurements_data_v1_client-1.1.0-py3-none-any.whl", hash = "sha256:dfc38d56fdd710a930fbc05383d43ab96f64aee545937f9ef605a3bb6f88cc31"}, - {file = "ni_measurements_data_v1_client-1.1.0.tar.gz", hash = "sha256:16b1ac8b82277e41719aa8b1aeae3f1040dba47e63651f83e30fe115cd036278"}, + {file = "ni_measurements_data_v1_client-1.2.0.dev0-py3-none-any.whl", hash = "sha256:ad6292348984847bc919e7083f3512033c415dbaa0a112e4466c3f8d4ca9e3d8"}, + {file = "ni_measurements_data_v1_client-1.2.0.dev0.tar.gz", hash = "sha256:8b6c40221fca3b9a258d734cab86d4c1511264519c32ed4c21e521396eb2f347"}, ] [package.dependencies] ni-measurementlink-discovery-v1-client = ">=1.1.0" -ni-measurements-data-v1-proto = ">=1.1.0" +ni-measurements-data-v1-proto = ">=1.2.0.dev0" [[package]] name = "ni-measurements-data-v1-proto" -version = "1.1.0" +version = "1.2.0.dev0" description = "Protobuf data types and service stubs for NI data store gRPC APIs" optional = false python-versions = "<4.0,>=3.10" groups = ["main"] files = [ - {file = "ni_measurements_data_v1_proto-1.1.0-py3-none-any.whl", hash = "sha256:06096cea733717b60281c92d0a1b44eac97194781507ba4fd9b3aa080a4450df"}, - {file = "ni_measurements_data_v1_proto-1.1.0.tar.gz", hash = "sha256:df1ca7ce3603dd5a0adc8795f0844da9a78ddc026f219bf596957cf3ed08da3d"}, + {file = "ni_measurements_data_v1_proto-1.2.0.dev0-py3-none-any.whl", hash = "sha256:d0e5331bdad20c3f812b4a1801969f390497213d0c7988f058c7761eb886e700"}, + {file = "ni_measurements_data_v1_proto-1.2.0.dev0.tar.gz", hash = "sha256:a10b15ddd7dd1196db51b6752653ffec487297db7812602346505771b7b37299"}, ] [package.dependencies] ni-datamonikers-v1-proto = ">=1.0.0" ni-measurements-metadata-v1-proto = ">=1.0.0" -ni-protobuf-types = ">=1.2.0" +ni-protobuf-types = ">=1.3.0.dev0" protobuf = ">=4.21" [[package]] @@ -2528,14 +2528,14 @@ protobuf = ">=4.21" [[package]] name = "ni-protobuf-types" -version = "1.2.0" +version = "1.3.0.dev0" description = "Protobuf data types for NI gRPC APIs" optional = false python-versions = "<4.0,>=3.10" groups = ["main"] files = [ - {file = "ni_protobuf_types-1.2.0-py3-none-any.whl", hash = "sha256:461c4825571d0054fd5427664403c3d69fcd180c9c00868ac1911693dd9bf901"}, - {file = "ni_protobuf_types-1.2.0.tar.gz", hash = "sha256:e8226f8ef44b104ffb1b1f6e416511c416be4d91bf5c2633db7ab58294e037bb"}, + {file = "ni_protobuf_types-1.3.0.dev0-py3-none-any.whl", hash = "sha256:a1851f0bccf7da857e1745933e5517fd6d95a79a79141d108fce42af958bde35"}, + {file = "ni_protobuf_types-1.3.0.dev0.tar.gz", hash = "sha256:674b4930dbb6baff6183df9bb4f90f3a3a0723bbe97a4cdeb3962163c220bc23"}, ] [package.dependencies] @@ -4726,4 +4726,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "1bda39cb633d744840c96991f1bcb283cf0e1faddfa49db451bf942399a31ee8" +content-hash = "1fe2999dd0533f875d6528337b9d72247e3f7e6bebe09f1bb767ed95dda8ef6d" diff --git a/pyproject.toml b/pyproject.toml index f278ba5..e2c381f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,9 +40,9 @@ requires-poetry = '>=2.1,<3.0' [tool.poetry.dependencies] python = "^3.10" protobuf = {version=">=4.21"} -ni-measurements-data-v1-client = { version = ">=1.1.0", allow-prereleases = true } +ni-measurements-data-v1-client = { version = ">=1.2.0.dev0", allow-prereleases = true } ni-measurements-metadata-v1-client = { version = ">=1.0.0" } -ni-protobuf-types = { version = ">=1.2.0", allow-prereleases = true } +ni-protobuf-types = { version = ">=1.3.0.dev0", allow-prereleases = true } hightime = { version = ">=1.0.0" } [tool.poetry.group.dev.dependencies] diff --git a/src/ni/datastore/data/_data_store_client.py b/src/ni/datastore/data/_data_store_client.py index bd9b176..c7dd29c 100644 --- a/src/ni/datastore/data/_data_store_client.py +++ b/src/ni/datastore/data/_data_store_client.py @@ -227,12 +227,15 @@ def publish_measurement( - Scalar: Single float, int, str or boolean - Vector: Array of float, int, str or boolean values - - DoubleAnalogWaveform: Analog waveform with double precision + - AnalogWaveform[np.float64]: Analog waveform with double precision + - AnalogWaveform[np.float32]: Analog waveform with single precision - DoubleXYData: XY coordinate data with double precision - - I16AnalogWaveform: Analog waveform with 16-bit integer precision - - DoubleComplexWaveform: Complex waveform with double precision - - I16ComplexWaveform: Complex waveform with 16-bit integer precision - - DoubleSpectrum: Frequency spectrum data with double precision + - AnalogWaveform[np.int16]: Analog waveform with 16-bit integer precision + - ComplexWaveform[np.complex128]: Complex waveform with double precision + - ComplexWaveform[np.complex64]: Complex waveform with single precision + - ComplexWaveform[ComplexInt32DType]: Complex waveform with 16-bit integer precision + - Spectrum[np.float64]: Frequency spectrum data with double precision + - Spectrum[np.float32]: Frequency spectrum data with single precision - DigitalWaveform: Digital waveform data step_id: The ID of the step associated with this measurement. This diff --git a/src/ni/datastore/data/_grpc_conversion.py b/src/ni/datastore/data/_grpc_conversion.py index 1d36795..85cab7e 100644 --- a/src/ni/datastore/data/_grpc_conversion.py +++ b/src/ni/datastore/data/_grpc_conversion.py @@ -25,6 +25,12 @@ from ni.protobuf.types.waveform_conversion import ( digital_waveform_from_protobuf, digital_waveform_to_protobuf, + float32_analog_waveform_from_protobuf, + float32_analog_waveform_to_protobuf, + float32_complex_waveform_from_protobuf, + float32_complex_waveform_to_protobuf, + float32_spectrum_from_protobuf, + float32_spectrum_to_protobuf, float64_analog_waveform_from_protobuf, float64_analog_waveform_to_protobuf, float64_complex_waveform_from_protobuf, @@ -241,6 +247,10 @@ def populate_publish_measurement_request_value( publish_request.double_analog_waveform.CopyFrom( float64_analog_waveform_to_protobuf(value) ) + elif value.dtype == np.float32: + publish_request.float_analog_waveform.CopyFrom( + float32_analog_waveform_to_protobuf(value) + ) elif value.dtype == np.int16: publish_request.i16_analog_waveform.CopyFrom(int16_analog_waveform_to_protobuf(value)) else: @@ -250,6 +260,10 @@ def populate_publish_measurement_request_value( publish_request.double_complex_waveform.CopyFrom( float64_complex_waveform_to_protobuf(value) ) + elif value.dtype == np.complex64: + publish_request.float_complex_waveform.CopyFrom( + float32_complex_waveform_to_protobuf(value) + ) elif value.dtype == ComplexInt32DType: publish_request.i16_complex_waveform.CopyFrom(int16_complex_waveform_to_protobuf(value)) else: @@ -257,6 +271,8 @@ def populate_publish_measurement_request_value( elif isinstance(value, Spectrum): if value.dtype == np.float64: publish_request.double_spectrum.CopyFrom(float64_spectrum_to_protobuf(value)) + elif value.dtype == np.float32: + publish_request.float_spectrum.CopyFrom(float32_spectrum_to_protobuf(value)) else: raise TypeError(f"Unsupported Spectrum dtype: {value.dtype}") elif isinstance(value, DigitalWaveform): @@ -338,10 +354,16 @@ def convert_read_measurement_response_from_protobuf( return digital_waveform_from_protobuf(response.digital_waveform) elif read_data_type == "double_analog_waveform": return float64_analog_waveform_from_protobuf(response.double_analog_waveform) + elif read_data_type == "float_analog_waveform": + return float32_analog_waveform_from_protobuf(response.float_analog_waveform) elif read_data_type == "double_complex_waveform": return float64_complex_waveform_from_protobuf(response.double_complex_waveform) + elif read_data_type == "float_complex_waveform": + return float32_complex_waveform_from_protobuf(response.float_complex_waveform) elif read_data_type == "double_spectrum": return float64_spectrum_from_protobuf(response.double_spectrum) + elif read_data_type == "float_spectrum": + return float32_spectrum_from_protobuf(response.float_spectrum) elif read_data_type == "i16_analog_waveform": return int16_analog_waveform_from_protobuf(response.i16_analog_waveform) elif read_data_type == "i16_complex_waveform": diff --git a/tests/acceptance/test_publish_measurement_and_read_data.py b/tests/acceptance/test_publish_measurement_and_read_data.py index 01dcb41..8df1912 100644 --- a/tests/acceptance/test_publish_measurement_and_read_data.py +++ b/tests/acceptance/test_publish_measurement_and_read_data.py @@ -88,11 +88,11 @@ def test___publish_xydata___read_measurement_value_returns_xydata( assert xydata == expected_xydata -def test___publish_spectrum___read_measurement_value_returns_spectrum( +def test___publish_float64_spectrum___read_measurement_value_returns_spectrum( acceptance_test_context: DataStoreContext, ) -> None: with DataStoreClient() as data_store_client: - step_id = _create_step(data_store_client, "spectrum") + step_id = _create_step(data_store_client, "float64 spectrum") expected_spectrum = Spectrum.from_array_1d( array=[1.0, 10.0, 100.0], dtype=np.float64, @@ -101,7 +101,7 @@ def test___publish_spectrum___read_measurement_value_returns_spectrum( ) published_measurement_id = data_store_client.publish_measurement( - name="python publish spectrum", + name="python publish float64 spectrum", value=expected_spectrum, step_id=step_id, ) @@ -113,11 +113,36 @@ def test___publish_spectrum___read_measurement_value_returns_spectrum( assert spectrum == expected_spectrum -def test___publish_analog_waveform___read_measurement_value_returns_analog_waveform( +def test___publish_float32_spectrum___read_measurement_value_returns_spectrum( acceptance_test_context: DataStoreContext, ) -> None: with DataStoreClient() as data_store_client: - step_id = _create_step(data_store_client, "analog waveform") + step_id = _create_step(data_store_client, "float32 spectrum") + expected_spectrum = Spectrum.from_array_1d( + array=[1.0, 10.0, 100.0], + dtype=np.float32, + start_frequency=1.0, + frequency_increment=1.0, + ) + + published_measurement_id = data_store_client.publish_measurement( + name="python publish spectrum float", + value=expected_spectrum, + step_id=step_id, + ) + + published_measurement = data_store_client.get_measurement(published_measurement_id) + spectrum = data_store_client.read_measurement_value( + published_measurement, expected_type=Spectrum + ) + assert spectrum == expected_spectrum + + +def test___publish_float64_analog_waveform___read_measurement_value_returns_float64_analog_waveform( + acceptance_test_context: DataStoreContext, +) -> None: + with DataStoreClient() as data_store_client: + step_id = _create_step(data_store_client, "float64 analog waveform") expected_waveform = AnalogWaveform( sample_count=3, raw_data=np.array([1.0, 2.0, 3.0]), @@ -125,7 +150,32 @@ def test___publish_analog_waveform___read_measurement_value_returns_analog_wavef ) published_measurement_id = data_store_client.publish_measurement( - name="python publish analog waveform", + name="python publish float64 analog waveform", + value=expected_waveform, + step_id=step_id, + ) + + published_measurement = data_store_client.get_measurement(published_measurement_id) + waveform = data_store_client.read_measurement_value( + published_measurement, expected_type=AnalogWaveform + ) + assert waveform == expected_waveform + + +def test___publish_float32_analog_waveform___read_measurement_value_returns_float32_analog_waveform( + acceptance_test_context: DataStoreContext, +) -> None: + with DataStoreClient() as data_store_client: + step_id = _create_step(data_store_client, "float32 analog waveform") + expected_waveform = AnalogWaveform( + sample_count=3, + dtype=np.float32, + raw_data=np.array([1.0, 2.0, 3.0], dtype=np.float32), + timing=Timing(SampleIntervalMode.NONE, time_offset=ht.timedelta()), + ) + + published_measurement_id = data_store_client.publish_measurement( + name="python publish float32 analog waveform", value=expected_waveform, step_id=step_id, ) @@ -159,17 +209,40 @@ def test___publish_digital_waveform___read_measurement_value_returns_digital_wav assert waveform == expected_waveform -def test___publish_complex_waveform___read_measurement_value_returns_complex_waveform( +def test___publish_float64_complex_waveform___read_measurement_value_returns_float64_complex_waveform( + acceptance_test_context: DataStoreContext, +) -> None: + with DataStoreClient() as data_store_client: + step_id = _create_step(data_store_client, "float64 complex waveform") + expected_waveform = ComplexWaveform( + 10, + timing=Timing(SampleIntervalMode.NONE, time_offset=ht.timedelta()), + ) + published_measurement_id = data_store_client.publish_measurement( + name="python publish float64 complex waveform", + value=expected_waveform, + step_id=step_id, + ) + + published_measurement = data_store_client.get_measurement(published_measurement_id) + waveform = data_store_client.read_measurement_value( + published_measurement, expected_type=ComplexWaveform + ) + assert waveform == expected_waveform + + +def test___publish_float32_complex_waveform___read_measurement_value_returns_float32_complex_waveform( acceptance_test_context: DataStoreContext, ) -> None: with DataStoreClient() as data_store_client: - step_id = _create_step(data_store_client, "complex waveform") + step_id = _create_step(data_store_client, "float32 complex waveform") expected_waveform = ComplexWaveform( 10, + dtype=np.complex64, timing=Timing(SampleIntervalMode.NONE, time_offset=ht.timedelta()), ) published_measurement_id = data_store_client.publish_measurement( - name="python publish complex waveform", + name="python publish float32 complex waveform", value=expected_waveform, step_id=step_id, ) diff --git a/tests/unit/data/test_grpc_conversion.py b/tests/unit/data/test_grpc_conversion.py index ffaabc7..bfe85de 100644 --- a/tests/unit/data/test_grpc_conversion.py +++ b/tests/unit/data/test_grpc_conversion.py @@ -186,6 +186,17 @@ def test___python_float64_analog_waveform___populate_measurement___measurement_u assert list(request.double_analog_waveform.y_data) == [0.0, 0.0, 0.0] +def test___python_float32_analog_waveform___populate_measurement___measurement_updated_correctly() -> ( + None +): + wfm_obj = AnalogWaveform(3, np.float32) + request = PublishMeasurementRequest() + populate_publish_measurement_request_value(request, wfm_obj) + + assert isinstance(request.float_analog_waveform, waveform_pb2.FloatAnalogWaveform) + assert list(request.float_analog_waveform.y_data) == [0.0, 0.0, 0.0] + + def test___python_int16_analog_waveform___populate_measurement___measurement_updated_correctly() -> ( None ): @@ -208,6 +219,17 @@ def test___python_float64_complex_waveform___populate_measurement___measurement_ assert list(request.double_complex_waveform.y_data) == [0.0, 0.0, 0.0, 0.0] +def test___python_float32_complex_waveform___populate_measurement___measurement_updated_correctly() -> ( + None +): + wfm_obj = ComplexWaveform(2, np.complex64) + request = PublishMeasurementRequest() + populate_publish_measurement_request_value(request, wfm_obj) + + assert isinstance(request.float_complex_waveform, waveform_pb2.FloatComplexWaveform) + assert list(request.float_complex_waveform.y_data) == [0.0, 0.0, 0.0, 0.0] + + def test___python_int16_complex_waveform___populate_measurement___measurement_updated_correctly() -> ( None ): @@ -261,6 +283,20 @@ def test___python_float64_spectrum___populate_measurement___measurement_updated_ assert request.double_spectrum.frequency_increment == 10.0 +def test___python_float32_spectrum___populate_measurement___measurement_updated_correctly() -> None: + spectrum = Spectrum.from_array_1d(np.array([1.0, 2.0, 3.0]), dtype=np.float32) + spectrum.start_frequency = 100.0 + spectrum.frequency_increment = 10.0 + + request = PublishMeasurementRequest() + populate_publish_measurement_request_value(request, spectrum) + + assert isinstance(request.float_spectrum, waveform_pb2.FloatSpectrum) + assert list(request.float_spectrum.data) == [1.0, 2.0, 3.0] + assert request.float_spectrum.start_frequency == 100.0 + assert request.float_spectrum.frequency_increment == 10.0 + + def test___python_float64_xydata___populate_measurement___measurement_updated_correctly() -> None: xydata = XYData.from_arrays_1d( [1.0, 2.0], [3.0, 4.0], np.float64, x_units="Volts", y_units="Seconds" diff --git a/tests/unit/data/test_read.py b/tests/unit/data/test_read.py index ae96f2f..653a87d 100644 --- a/tests/unit/data/test_read.py +++ b/tests/unit/data/test_read.py @@ -59,6 +59,26 @@ def test___read_double_analog_waveform___value_correct( assert isinstance(actual_waveform, AnalogWaveform) assert list(actual_waveform.scaled_data) == list(expected_waveform.y_data) + assert actual_waveform.dtype == np.float64 + + +def test___read_float_analog_waveform___value_correct( + data_store_client: DataStoreClient, mocked_data_store_service_client: NonCallableMock +) -> None: + published_measurement = PublishedMeasurement(id="measurement-123") + expected_waveform = waveform_pb2.FloatAnalogWaveform(y_data=[1.0, 2.0, 3.0]) + response = Mock() + response.WhichOneof.return_value = "float_analog_waveform" + response.float_analog_waveform = expected_waveform + mocked_data_store_service_client.read_measurement_value.return_value = response + + actual_waveform = data_store_client.read_measurement_value( + published_measurement, AnalogWaveform + ) + + assert isinstance(actual_waveform, AnalogWaveform) + assert list(actual_waveform.scaled_data) == list(expected_waveform.y_data) + assert actual_waveform.dtype == np.float32 def test___read_i16_analog_waveform___value_correct( @@ -99,6 +119,27 @@ def test___read_double_complex_waveform___value_correct( assert actual_waveform.dtype == np.complex128 +def test___read_float_complex_waveform___value_correct( + data_store_client: DataStoreClient, mocked_data_store_service_client: NonCallableMock +) -> None: + published_measurement = PublishedMeasurement(id="measurement-789") + expected_waveform = waveform_pb2.FloatComplexWaveform(y_data=[1.0, 2.0, 3.0, 4.0]) + response = Mock() + response.WhichOneof.return_value = "float_complex_waveform" + response.float_complex_waveform = expected_waveform + mocked_data_store_service_client.read_measurement_value.return_value = response + + actual_waveform = data_store_client.read_measurement_value( + published_measurement, ComplexWaveform + ) + + assert isinstance(actual_waveform, ComplexWaveform) + assert actual_waveform.sample_count == actual_waveform.capacity == 2 + assert len(actual_waveform.raw_data) == 2 + assert list(actual_waveform.raw_data) == [1.0 + 2.0j, 3.0 + 4.0j] + assert actual_waveform.dtype == np.complex64 + + def test___read_i16_complex_waveform___value_correct( data_store_client: DataStoreClient, mocked_data_store_service_client: NonCallableMock ) -> None: @@ -159,6 +200,30 @@ def test___read_double_spectrum___value_correct( assert list(actual_waveform.data) == [1.0, 2.0, 3.0] assert actual_waveform.start_frequency == 100.0 assert actual_waveform.frequency_increment == 10.0 + assert actual_waveform.dtype == np.float64 + + +def test___read_float_spectrum___value_correct( + data_store_client: DataStoreClient, mocked_data_store_service_client: NonCallableMock +) -> None: + published_measurement = PublishedMeasurement(id="measurement-303") + expected_waveform = waveform_pb2.FloatSpectrum( + data=[1.0, 2.0, 3.0], + start_frequency=100.0, + frequency_increment=10.0, + ) + response = Mock() + response.WhichOneof.return_value = "float_spectrum" + response.float_spectrum = expected_waveform + mocked_data_store_service_client.read_measurement_value.return_value = response + + actual_waveform = data_store_client.read_measurement_value(published_measurement, Spectrum) + + assert isinstance(actual_waveform, Spectrum) + assert list(actual_waveform.data) == [1.0, 2.0, 3.0] + assert actual_waveform.start_frequency == 100.0 + assert actual_waveform.frequency_increment == 10.0 + assert actual_waveform.dtype == np.float32 def test___read_vector___value_correct(