Skip to content

Commit 2eb2c66

Browse files
authored
Add XYData conversion methods (#192)
* Add XYData conversion methods Signed-off-by: Michael Johansen <michael.johansen@emerson.com> * Narrow XYData subtype to np.float64 Signed-off-by: Michael Johansen <michael.johansen@emerson.com> --------- Signed-off-by: Michael Johansen <michael.johansen@emerson.com>
1 parent 56f7054 commit 2eb2c66

4 files changed

Lines changed: 175 additions & 6 deletions

File tree

packages/ni.protobuf.types/poetry.lock

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/ni.protobuf.types/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ requires-poetry = '>=2.1,<3.0'
3939

4040
[tool.poetry.dependencies]
4141
protobuf = {version=">=4.21"}
42-
nitypes = {version=">=0.1.0dev8", allow-prereleases=true}
42+
nitypes = {version=">=1.1.0dev0", allow-prereleases=true}
4343

4444
[tool.poetry.group.dev.dependencies]
4545
types-grpcio = ">=1.0"
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Methods to convert to and from DoubleXYData protobuf messages."""
2+
3+
from __future__ import annotations
4+
5+
import numpy as np
6+
from nitypes.xy_data import XYData
7+
8+
import ni.protobuf.types.xydata_pb2 as xydata_pb2
9+
from ni.protobuf.types.extended_property_conversion import (
10+
extended_properties_from_protobuf,
11+
extended_properties_to_protobuf,
12+
)
13+
14+
15+
def float64_xydata_to_protobuf(value: XYData[np.float64], /) -> xydata_pb2.DoubleXYData:
16+
"""Convert a XYData python object to a protobuf xydata_pb2.DoubleXYData."""
17+
attributes = extended_properties_to_protobuf(value.extended_properties)
18+
xydata_message = xydata_pb2.DoubleXYData(
19+
x_data=value.x_data,
20+
y_data=value.y_data,
21+
attributes=attributes,
22+
)
23+
return xydata_message
24+
25+
26+
def float64_xydata_from_protobuf(message: xydata_pb2.DoubleXYData, /) -> XYData[np.float64]:
27+
"""Convert the protobuf xydata_pb2.DoubleXYData to a Python XYData."""
28+
xydata = XYData.from_arrays_1d(
29+
x_array=message.x_data,
30+
y_array=message.y_data,
31+
dtype=np.float64,
32+
)
33+
34+
# Transfer attributes to extended_properties
35+
extended_properties_from_protobuf(message.attributes, xydata.extended_properties)
36+
37+
return xydata
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import numpy as np
2+
from nitypes.xy_data import XYData
3+
4+
import ni.protobuf.types.xydata_pb2 as xydata_pb2
5+
from ni.protobuf.types.attribute_value_pb2 import AttributeValue
6+
from ni.protobuf.types.xydata_conversion import (
7+
float64_xydata_from_protobuf,
8+
float64_xydata_to_protobuf,
9+
)
10+
11+
12+
# ========================================================
13+
# XYData: Protobuf to Python
14+
# ========================================================
15+
def test___doublexydata_protobuf___convert___valid_xydata_default_units() -> None:
16+
expected_x_data = [1.0, 2.0, 3.0]
17+
expected_y_data = [4.0, 5.0, 6.0]
18+
protobuf_value = xydata_pb2.DoubleXYData(x_data=expected_x_data, y_data=expected_y_data)
19+
20+
python_value = float64_xydata_from_protobuf(protobuf_value)
21+
assert isinstance(python_value, XYData)
22+
assert python_value.dtype == np.float64
23+
assert list(python_value.x_data) == expected_x_data
24+
assert list(python_value.y_data) == expected_y_data
25+
assert python_value.x_units == ""
26+
assert python_value.y_units == ""
27+
28+
29+
def test___doublexydata_protobuf_with_units___convert___valid_xydata() -> None:
30+
attributes = {
31+
"NI_UnitDescription_X": AttributeValue(string_value="Volts"),
32+
"NI_UnitDescription_Y": AttributeValue(string_value="Seconds"),
33+
}
34+
expected_x_data = [1.0, 2.0, 3.0]
35+
expected_y_data = [4.0, 5.0, 6.0]
36+
protobuf_value = xydata_pb2.DoubleXYData(
37+
x_data=expected_x_data,
38+
y_data=expected_y_data,
39+
attributes=attributes,
40+
)
41+
42+
python_value = float64_xydata_from_protobuf(protobuf_value)
43+
44+
assert isinstance(python_value, XYData)
45+
assert python_value.dtype == np.float64
46+
assert list(python_value.x_data) == expected_x_data
47+
assert list(python_value.y_data) == expected_y_data
48+
assert python_value.x_units == "Volts"
49+
assert python_value.y_units == "Seconds"
50+
51+
52+
def test___doublexydata_protobuf_with_other_attrs___convert___attrs_converted() -> None:
53+
attributes = {
54+
"NI_UnitDescription_X": AttributeValue(string_value="Volts"),
55+
"NI_UnitDescription_Y": AttributeValue(string_value="Seconds"),
56+
"Non_Units_Attribute": AttributeValue(double_value=1.1),
57+
}
58+
protobuf_value = xydata_pb2.DoubleXYData(
59+
x_data=[1.0],
60+
y_data=[2.0],
61+
attributes=attributes,
62+
)
63+
64+
python_value = float64_xydata_from_protobuf(protobuf_value)
65+
66+
assert isinstance(python_value, XYData)
67+
assert python_value.x_units == "Volts"
68+
assert python_value.y_units == "Seconds"
69+
assert python_value.extended_properties.get("Non_Units_Attribute") == 1.1
70+
71+
72+
# ========================================================
73+
# XYData: Python to Protobuf
74+
# ========================================================
75+
def test___float64_xydata___convert___valid_doublexydata_protobuf() -> None:
76+
expected_x_data = [1.0, 2.0, 3.0]
77+
expected_y_data = [4.0, 5.0, 6.0]
78+
python_value = XYData.from_arrays_1d(
79+
x_array=expected_x_data,
80+
y_array=expected_y_data,
81+
dtype=np.float64,
82+
)
83+
84+
protobuf_value = float64_xydata_to_protobuf(python_value)
85+
86+
assert isinstance(protobuf_value, xydata_pb2.DoubleXYData)
87+
assert list(protobuf_value.x_data) == expected_x_data
88+
assert list(protobuf_value.y_data) == expected_y_data
89+
90+
91+
def test___int16_xydata___convert___causes_mypy_error() -> None:
92+
python_value = XYData.from_arrays_1d(
93+
x_array=[1, 2, 3],
94+
y_array=[4, 5, 6],
95+
dtype=np.int16,
96+
)
97+
98+
# The next line should generate a mypy error. If it doesn't, we'll get an 'unused
99+
# ignore' mypy error.
100+
protobuf_value = float64_xydata_to_protobuf(python_value) # type: ignore[arg-type]
101+
102+
# This conversion still works, so we might as well check it. Int values are converted to float.
103+
assert isinstance(protobuf_value, xydata_pb2.DoubleXYData)
104+
assert list(protobuf_value.x_data) == [1.0, 2.0, 3.0]
105+
assert list(protobuf_value.y_data) == [4.0, 5.0, 6.0]
106+
107+
108+
def test___xydata_with_extended_properties___convert___valid_doublexydata_protobuf() -> None:
109+
expected_x_data = [1.0]
110+
expected_y_data = [2.0]
111+
python_value = XYData.from_arrays_1d(
112+
x_array=expected_x_data,
113+
y_array=expected_y_data,
114+
dtype=np.float64,
115+
extended_properties={
116+
"true": True,
117+
"1": 1,
118+
"1.0": 1.0,
119+
"str": "str",
120+
},
121+
)
122+
123+
protobuf_value = float64_xydata_to_protobuf(python_value)
124+
125+
assert isinstance(protobuf_value, xydata_pb2.DoubleXYData)
126+
assert list(protobuf_value.x_data) == expected_x_data
127+
assert list(protobuf_value.y_data) == expected_y_data
128+
assert protobuf_value.attributes["true"].bool_value is True
129+
assert protobuf_value.attributes["1"].integer_value == 1
130+
assert protobuf_value.attributes["1.0"].double_value == 1.0
131+
assert protobuf_value.attributes["str"].string_value == "str"

0 commit comments

Comments
 (0)