Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add spatial_data to csm serialization #364

Merged
merged 15 commits into from
Jun 22, 2021
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
- add `core/graph/di_node`, `core/graph/di_edge` & `core/graph/di_graph` for implementing a
generic `networkx.DiGraph` [[#330]](https://github.com/BAMWelDX/weldx/pull/330)
- compatibility with ASDF-2.8 [[#355]](https://github.com/BAMWelDX/weldx/pull/355)

- data attached to an instance of the `CoordinateSystemManger` is now also stored in a WelDX file
[[#364]](https://github.com/BAMWelDX/weldx/pull/339)

## 0.3.3 (30.03.2021)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,20 @@ properties:
items:
tag: "tag:weldx.bam.de:weldx/core/transformations/coordinate_transformation-1.0.0"

propertyOrder: [name, root_system_name, reference_time, subsystems, subsystem_data, coordinate_systems]
spatial_data:
type: array
items:
type: object
properties:
coordinate_system:
type: string
name:
type: string
data:
tag: "tag:weldx.bam.de:weldx/core/geometry/spatial_data-1.0.0"
vhirtham marked this conversation as resolved.
Show resolved Hide resolved
required: [coordinate_system, name, data]

propertyOrder: [name, root_system_name, reference_time, subsystems, subsystem_data, coordinate_systems, spatial_data]
required: [name, root_system_name, coordinate_systems]
flowStyle: block
...
Original file line number Diff line number Diff line change
Expand Up @@ -406,13 +406,21 @@ def to_tree(cls, node: CoordinateSystemManager, ctx):
if subsystem.parent_system == node.name
]

spatial_data = None
if len(node._data) > 0:
spatial_data = [
dict(name=k, coordinate_system=v.coordinate_system_name, data=v.data)
for k, v in node._data.items()
]

tree = {
"name": node.name,
"reference_time": node.reference_time,
"subsystem_names": subsystems,
"subsystems": subsystem_data,
"root_system_name": node.root_system_name,
"coordinate_systems": coordinate_system_data,
"spatial_data": spatial_data,
}
return tree

Expand Down Expand Up @@ -458,4 +466,8 @@ def from_tree(cls, tree, ctx):
cls._add_coordinate_systems_to_subsystems(tree, csm, subsystem_data_list)
cls._merge_subsystems(tree, csm, subsystem_data_list)

if (spatial_data := tree.get("spatial_data")) is not None:
for item in spatial_data:
csm.assign_data(item["data"], item["name"], item["coordinate_system"])

return csm
10 changes: 10 additions & 0 deletions weldx/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2330,6 +2330,16 @@ def __post_init__(self):
if not self.triangles.ndim == 2:
raise ValueError("SpatialData triangulation must be a 2d array")

# def __eq__(self, other):
# """Compare to another object."""
# if not isinstance(other, SpatialData):
# return False
# if not self.coordinates.data == other.coordinates.data:
# return False
# if self.triangles and not self.triangles == other.triangles:
# return False
# if not self.attributes ==

vhirtham marked this conversation as resolved.
Show resolved Hide resolved
@staticmethod
def from_file(file_name: Union[str, Path]) -> "SpatialData":
"""Create an instance from a file.
Expand Down
65 changes: 52 additions & 13 deletions weldx/tests/asdf_tests/test_asdf_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import weldx.transformations as tf
from weldx.asdf.tags.weldx.core.file import ExternalFile
from weldx.asdf.util import _write_buffer, _write_read_buffer
from weldx.asdf.util import _write_buffer, write_read_buffer
from weldx.constants import WELDX_QUANTITY as Q_
from weldx.core import MathematicalExpression as ME # nopep8
from weldx.core import TimeSeries
Expand Down Expand Up @@ -52,7 +52,7 @@
],
)
def test_rotation(inputs):
data = _write_read_buffer({"rot": inputs})
data = write_read_buffer({"rot": inputs})
r = data["rot"]
assert np.allclose(r.as_quat(), inputs.as_quat())
if hasattr(inputs, "wx_meta"):
Expand All @@ -79,7 +79,7 @@ def test_rotation_euler_prefix(inputs):
"""Test unit prefix handling."""
degrees = "degree" in str(inputs.u)
rot = WXRotation.from_euler(seq="x", angles=inputs)
data = _write_read_buffer({"rot": rot})
data = write_read_buffer({"rot": rot})
r = data["rot"].as_euler("xyz", degrees=degrees)[0]
r = Q_(r, "degree") if degrees else Q_(r, "rad")
assert np.allclose(inputs, r)
Expand Down Expand Up @@ -118,7 +118,7 @@ def test_xarray_data_array(copy_arrays, lazy_load):
"""Test ASDF read/write of xarray.DataArray."""
dax = get_xarray_example_data_array()
tree = {"dax": dax}
dax_file = _write_read_buffer(
dax_file = write_read_buffer(
tree, open_kwargs={"copy_arrays": copy_arrays, "lazy_load": lazy_load}
)["dax"]
assert dax.identical(dax_file)
Expand Down Expand Up @@ -168,7 +168,7 @@ def get_xarray_example_dataset():
def test_xarray_dataset(copy_arrays, lazy_load):
dsx = get_xarray_example_dataset()
tree = {"dsx": dsx}
dsx_file = _write_read_buffer(
dsx_file = write_read_buffer(
tree, open_kwargs={"copy_arrays": copy_arrays, "lazy_load": lazy_load}
)["dsx"]
assert dsx.identical(dsx_file)
Expand Down Expand Up @@ -227,7 +227,7 @@ def test_local_coordinate_system(
):
"""Test (de)serialization of LocalCoordinateSystem in ASDF."""
lcs = get_local_coordinate_system(time_dep_orientation, time_dep_coordinates)
data = _write_read_buffer(
data = write_read_buffer(
{"lcs": lcs}, open_kwargs={"copy_arrays": copy_arrays, "lazy_load": lazy_load}
)
assert data["lcs"] == lcs
Expand Down Expand Up @@ -299,7 +299,7 @@ def get_example_coordinate_system_manager():
def test_coordinate_system_manager(copy_arrays, lazy_load):
csm = get_example_coordinate_system_manager()
tree = {"cs_hierarchy": csm}
data = _write_read_buffer(
data = write_read_buffer(
tree, open_kwargs={"copy_arrays": copy_arrays, "lazy_load": lazy_load}
)
csm_file = data["cs_hierarchy"]
Expand Down Expand Up @@ -361,7 +361,7 @@ def get_coordinate_system_manager_with_subsystems(nested: bool):
def test_coordinate_system_manager_with_subsystems(copy_arrays, lazy_load, nested):
csm = get_coordinate_system_manager_with_subsystems(nested)
tree = {"cs_hierarchy": csm}
data = _write_read_buffer(
data = write_read_buffer(
tree, open_kwargs={"copy_arrays": copy_arrays, "lazy_load": lazy_load}
)
csm_file = data["cs_hierarchy"]
Expand Down Expand Up @@ -403,13 +403,52 @@ def test_coordinate_system_manager_time_dependencies(
csm_root.merge(csm_sub_2)

tree = {"cs_hierarchy": csm_root}
data = _write_read_buffer(
data = write_read_buffer(
tree, open_kwargs={"copy_arrays": copy_arrays, "lazy_load": lazy_load}
)
csm_file = data["cs_hierarchy"]
assert csm_root == csm_file


@pytest.mark.parametrize("copy_arrays", [True, False])
@pytest.mark.parametrize("lazy_load", [True, False])
def test_coordinate_system_manager_with_data(copy_arrays, lazy_load):
"""Test if data attached to a CSM is stored and read correctly."""
csm = tf.CoordinateSystemManager("root", "csm")
csm.create_cs("cs_1", "root", coordinates=[1, 1, 1])
csm.create_cs("cs_2", "root", coordinates=[-1, -1, -1])
csm.create_cs("cs_11", "cs_1", coordinates=[1, 1, 1])

data_11 = SpatialData(coordinates=np.array([[1.0, 2.0, 3.0], [3.0, 2.0, 1.0]]))
data_2 = SpatialData(
coordinates=np.array(
[
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
]
),
triangles=np.array([[0, 1, 2], [0, 2, 3]], dtype="uint32"),
)

csm.assign_data(data_11, "data_11", "cs_11")
csm.assign_data(data_2, "data_2", "cs_2")

tree = {"csm": csm}
buffer = write_read_buffer(
tree, open_kwargs={"copy_arrays": copy_arrays, "lazy_load": lazy_load}
)
csm_buffer = buffer["csm"]

for data_name in csm.data_names:
sd = csm.get_data(data_name)
sd_buffer = csm_buffer.get_data(data_name)
assert np.allclose(sd.coordinates.data, sd_buffer.coordinates.data)
if sd.triangles is not None:
assert np.allclose(np.array(sd.triangles), np.array(sd_buffer.triangles))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use == here once implemented

Copy link
Collaborator Author

@vhirtham vhirtham Jun 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't compare equal:

SpatialData(coordinates=<xarray.DataArray 'core/ndarray' (n: 2, c: 3)>
vs.
SpatialData(coordinates=<xarray.DataArray (n: 2, c: 3)>

The data seems to be equal, but since compare nested is using xarrays identical function, we get a False returned.
I remember that we had trouble with the identical function before because it also considers the order of dimensions, coordinates etc. Alternatively, we can use equals for a more relaxed comparison but I am not sure if this is really what we want.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this happens because of the .name property that get's created when reading arrays from asdf files (due to NDArrayType wrapper)

see #110 and a possible workaround (reading explicitly as np.asarray from tree here 1dd1f23)

maybe we should ask for a solution over at asdf

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try the workaround for now.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works now. Think we can merge



# --------------------------------------------------------------------------------------
# TimeSeries
# --------------------------------------------------------------------------------------
Expand All @@ -428,7 +467,7 @@ def test_coordinate_system_manager_time_dependencies(
],
)
def test_time_series_discrete(ts, copy_arrays, lazy_load):
ts_file = _write_read_buffer(
ts_file = write_read_buffer(
{"ts": ts}, open_kwargs={"copy_arrays": copy_arrays, "lazy_load": lazy_load}
)["ts"]
if isinstance(ts.data, ME):
Expand Down Expand Up @@ -616,7 +655,7 @@ def test_asdf_serialization(copy_arrays, lazy_load, store_content):
asdf_save_content=store_content,
)
tree = {"file": ef}
ef_file = _write_read_buffer(
ef_file = write_read_buffer(
tree, open_kwargs={"copy_arrays": copy_arrays, "lazy_load": lazy_load}
)["file"]

Expand Down Expand Up @@ -661,7 +700,7 @@ def test_asdf_serialization(copy_arrays, lazy_load):

pc = SpatialData(coordinates=coordinates, triangles=triangles)
tree = {"point_cloud": pc}
pc_file = _write_read_buffer(
pc_file = write_read_buffer(
tree, open_kwargs={"copy_arrays": copy_arrays, "lazy_load": lazy_load}
)["point_cloud"]

Expand All @@ -678,7 +717,7 @@ def test_graph_serialization():
g.add_edges_from(
[("A", "B"), ("A", "C"), ("A", "F"), ("D", "C"), ("B", "H"), ("X", "A")]
)
g2 = _write_read_buffer({"graph": g})["graph"]
g2 = write_read_buffer({"graph": g})["graph"]

assert all(e in g.edges for e in g2.edges)
assert all(n in g.nodes for n in g2.nodes)
1 change: 1 addition & 0 deletions weldx/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""pytest configuration."""
import pytest

from weldx.asdf.cli.welding_schema import single_pass_weld_example


Expand Down
2 changes: 1 addition & 1 deletion weldx/transformations/cs_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1149,7 +1149,7 @@ def data_names(self) -> List[str]:

def get_data(
self, data_name, target_coordinate_system_name=None
) -> Union[np.ndarray, xr.DataArray]:
) -> Union[np.ndarray, SpatialData]:
"""Get the specified data, optionally transformed into any coordinate system.

Parameters
Expand Down