From 930c5f9d0cab6a88fb6704b8e0b7c4562954381c Mon Sep 17 00:00:00 2001 From: CagtayFabry <43667554+CagtayFabry@users.noreply.github.com> Date: Mon, 13 Jul 2020 10:21:46 +0200 Subject: [PATCH] Measurement asdf (#70) * Add draft for mathematical function class based on SymPy * Add serialization of mathematical function class * Add measurement example notebook * Add example notebook with quantities * Add allOf syntax to validator testcase * Add unit support to LCS --- .typo-ci.yml | 6 + CHANGELOG.md | 12 + environment.yml | 1 + setup.py | 1 + tests/asdf_tests/test_core.py | 20 +- tests/test_measurement.py | 120 ++++ tutorials/measurement_example.ipynb | 541 ++++++++++++++++++ weldx/__init__.py | 3 +- .../core/mathematical_expression-1.0.0.yaml | 25 + .../debug/validator_testclass-1.0.0.yaml | 5 +- .../equipment/generic_equipment-1.0.0.yaml | 30 + .../weldx/measurement/data-1.0.0.yaml | 24 + .../data_transformation-1.0.0.yaml | 30 + .../weldx/measurement/error-1.0.0.yaml | 25 + .../weldx/measurement/measurement-1.0.0.yaml | 27 + .../measurement/measurement_chain-1.0.0.yaml | 27 + .../weldx/measurement/signal-1.0.0.yaml | 24 + .../weldx/measurement/source-1.0.0.yaml | 25 + weldx/asdf/tags/weldx/__init__.py | 2 +- weldx/asdf/tags/weldx/core/__init__.py | 9 +- .../weldx/core/mathematical_expression.py | 89 +++ .../coordinate_system_hierarchy.py | 2 +- weldx/asdf/tags/weldx/debug/const_class.py | 2 +- .../tags/weldx/debug/validator_testclass.py | 2 +- weldx/asdf/tags/weldx/equipment/__init__.py | 1 + .../tags/weldx/equipment/generic_equipment.py | 28 + weldx/asdf/tags/weldx/measurement/__init__.py | 9 + weldx/asdf/tags/weldx/measurement/data.py | 26 + .../weldx/measurement/data_transformation.py | 26 + weldx/asdf/tags/weldx/measurement/error.py | 26 + .../tags/weldx/measurement/measurement.py | 26 + .../weldx/measurement/measurement_chain.py | 26 + weldx/asdf/tags/weldx/measurement/signal.py | 28 + weldx/asdf/tags/weldx/measurement/source.py | 26 + weldx/measurement.py | 80 +++ weldx/transformations.py | 3 +- 36 files changed, 1336 insertions(+), 21 deletions(-) create mode 100644 tests/test_measurement.py create mode 100644 tutorials/measurement_example.ipynb create mode 100644 weldx/asdf/schemas/weldx.bam.de/weldx/core/mathematical_expression-1.0.0.yaml create mode 100644 weldx/asdf/schemas/weldx.bam.de/weldx/equipment/generic_equipment-1.0.0.yaml create mode 100644 weldx/asdf/schemas/weldx.bam.de/weldx/measurement/data-1.0.0.yaml create mode 100644 weldx/asdf/schemas/weldx.bam.de/weldx/measurement/data_transformation-1.0.0.yaml create mode 100644 weldx/asdf/schemas/weldx.bam.de/weldx/measurement/error-1.0.0.yaml create mode 100644 weldx/asdf/schemas/weldx.bam.de/weldx/measurement/measurement-1.0.0.yaml create mode 100644 weldx/asdf/schemas/weldx.bam.de/weldx/measurement/measurement_chain-1.0.0.yaml create mode 100644 weldx/asdf/schemas/weldx.bam.de/weldx/measurement/signal-1.0.0.yaml create mode 100644 weldx/asdf/schemas/weldx.bam.de/weldx/measurement/source-1.0.0.yaml create mode 100644 weldx/asdf/tags/weldx/core/mathematical_expression.py create mode 100644 weldx/asdf/tags/weldx/equipment/__init__.py create mode 100644 weldx/asdf/tags/weldx/equipment/generic_equipment.py create mode 100644 weldx/asdf/tags/weldx/measurement/__init__.py create mode 100644 weldx/asdf/tags/weldx/measurement/data.py create mode 100644 weldx/asdf/tags/weldx/measurement/data_transformation.py create mode 100644 weldx/asdf/tags/weldx/measurement/error.py create mode 100644 weldx/asdf/tags/weldx/measurement/measurement.py create mode 100644 weldx/asdf/tags/weldx/measurement/measurement_chain.py create mode 100644 weldx/asdf/tags/weldx/measurement/signal.py create mode 100644 weldx/asdf/tags/weldx/measurement/source.py create mode 100644 weldx/measurement.py diff --git a/.typo-ci.yml b/.typo-ci.yml index 35ff23624..fd37dff01 100644 --- a/.typo-ci.yml +++ b/.typo-ci.yml @@ -20,6 +20,7 @@ excluded_words: - pandas - xarray - scipy + - sympy - pytest - pydocstyle - matplotlib @@ -79,6 +80,9 @@ excluded_words: - ipython # scipy --------------------------------------- - Slerp + # sympy --------------------------------------- + - lambdify + - sympify # welding ------------------------------------- - weldment # conda --------------------------------------- @@ -101,3 +105,5 @@ excluded_words: - xyz - bamwelding - netcdf + - msm + - Beckhoff diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f518e7ba..d160ef9c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,3 +15,15 @@ `weldx.transformations.CoordinateSystemManager`. - add test for `xarray.DataArray`, `xarray.Dataset`, `weldx.transformations.LocalCoordinateSystem` and `weldx.transformations.CoordinateSystemManager` serialization. +- allow using `pint.Quantity` coordinates in `weldx.transformations.LocalCoordinateSystem` [#70] +- add measurement related ASDF serialization classes: [#70] + - `equipment/generic_equipment-1.0.0` + - `measurement/data-1.0.0` + - `data_transformation-1.0.0` + - `measurement/error-1.0.0` + - `measurement/measurement-1.0.0` + - `measurement/measurement_chain-1.0.0` + - `measurement/signal-1.0.0` + - `measurement/source-1.0.0` +- add ASDF support for `sympy` expressions with `core/mathematical_expression-1.0.0` [#70] +- add example notebook for measurement chains in tutorials [#70] diff --git a/environment.yml b/environment.yml index baae8c26d..bd9b2ed8b 100644 --- a/environment.yml +++ b/environment.yml @@ -8,6 +8,7 @@ dependencies: - numpy>=1.18 - pandas>=1.0 - scipy>=1.4 + - sympy>=1.6 - xarray>=0.15 - pint>=0.11 - bottleneck diff --git a/setup.py b/setup.py index 46416ceab..5c200d937 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ "pandas>=1.0", "xarray>=0.15", "scipy>=1.4", + "sympy>=1.6", "pint>=0.11", "asdf>=2.6", "bottleneck>=1.3", diff --git a/tests/asdf_tests/test_core.py b/tests/asdf_tests/test_core.py index 0f74cdcd6..7837ab552 100644 --- a/tests/asdf_tests/test_core.py +++ b/tests/asdf_tests/test_core.py @@ -142,7 +142,7 @@ def get_local_coordinate_system(time_dep_orientation: bool, time_dep_coordinates A local coordinate system """ - coords = [2, 5, 1] + coords = Q_(np.asarray([2.0, 5.0, 1.0]), "mm") orientation = tf.rotation_matrix_z(np.pi / 3) if not time_dep_orientation and not time_dep_coordinates: @@ -247,22 +247,18 @@ def are_coordinate_system_managers_equal( def get_example_coordinate_system_manager(): """Get a consistent CoordinateSystemManager instance for test purposes.""" csm = tf.CoordinateSystemManager("root") - csm.add_coordinate_system( - "lcs_01", "root", tf.LocalCoordinateSystem(coordinates=[1, 2, 3]) - ) - csm.add_coordinate_system( + csm.create_cs("lcs_01", "root", coordinates=[1, 2, 3]) + csm.create_cs( "lcs_02", "root", - tf.LocalCoordinateSystem( - orientation=tf.rotation_matrix_z(np.pi / 3), coordinates=[4, -7, 8] - ), + orientation=tf.rotation_matrix_z(np.pi / 3), + coordinates=[4, -7, 8], ) - csm.add_coordinate_system( + csm.create_cs( "lcs_03", "lcs_02", - tf.LocalCoordinateSystem( - orientation=tf.rotation_matrix_y(np.pi / 11), coordinates=[4, -7, 8] - ), + orientation=tf.rotation_matrix_y(np.pi / 11), + coordinates=[4, -7, 8], ) return csm diff --git a/tests/test_measurement.py b/tests/test_measurement.py new file mode 100644 index 000000000..44af0aa65 --- /dev/null +++ b/tests/test_measurement.py @@ -0,0 +1,120 @@ +"""Test the measurement package.""" +from io import BytesIO + +import asdf +import sympy +import xarray as xr + +import weldx.measurement as msm +from weldx.asdf.extension import WeldxAsdfExtension, WeldxExtension +from weldx.asdf.tags.weldx.core.mathematical_expression import MathematicalExpression +from weldx.constants import WELDX_QUANTITY as Q_ + + +def test_generic_measurement(): + """Test basic measurement creation and ASDF read/write.""" + data_01 = msm.Data( + name="Welding current", data=xr.DataArray([1, 2, 3, 4], dims=["time"]) + ) + + data_02 = msm.Data( + name="Welding voltage", data=xr.DataArray([10, 20, 30, 40], dims=["time"]) + ) + + src_01 = msm.Source( + name="Current Sensor", + output_signal=msm.Signal("analog", "V", data=None), + error=msm.Error(1337.42), + ) + + src_02 = msm.Source( + name="Voltage Sensor", + output_signal=msm.Signal("analog", "V", data=None), + error=msm.Error(1), + ) + + dp_01 = msm.DataTransformation( + name="AD conversion current measurement", + input_signal=src_01.output_signal, + output_signal=msm.Signal("digital", "V", data=None), + error=msm.Error(999.0), + ) + + dp_02 = msm.DataTransformation( + name="Calibration current measurement", + input_signal=dp_01.output_signal, + output_signal=msm.Signal("digital", "A", data=data_01), + error=msm.Error(43.0), + ) + + dp_03 = msm.DataTransformation( + name="AD conversion voltage measurement", + input_signal=dp_02.output_signal, + output_signal=msm.Signal("digital", "V", data=None), + error=msm.Error(2.0), + ) + + dp_04 = msm.DataTransformation( + name="Calibration voltage measurement", + input_signal=dp_03.output_signal, + output_signal=msm.Signal("digital", "V", data=data_02), + error=msm.Error(Q_(3.0, "percent")), + ) + + chn_01 = msm.MeasurementChain( + name="Current measurement", data_source=src_01, data_processors=[dp_01, dp_02] + ) + + chn_02 = msm.MeasurementChain( + name="Voltage measurement", data_source=src_02, data_processors=[dp_03, dp_04] + ) + + eqp_01 = msm.GenericEquipment( + "Current Sensor", sources=[src_01], data_transformations=[dp_02] + ) + eqp_02 = msm.GenericEquipment( + "AD Converter", sources=None, data_transformations=[dp_01, dp_03] + ) + eqp_03 = msm.GenericEquipment( + "Voltage Sensor", sources=None, data_transformations=[dp_04] + ) + + measurement_01 = msm.Measurement( + name="Current measurement", data=[data_01], measurement_chain=chn_01 + ) + measurement_02 = msm.Measurement( + name="Voltage measurement", data=[data_02], measurement_chain=chn_02 + ) + + equipment = [eqp_01, eqp_02, eqp_03] + measurement_data = [data_01, data_02] + measurement_chains = [chn_01] + measurements = [measurement_01, measurement_02] + sources = [src_01] + processors = [dp_01, dp_02] + + [a, x, b] = sympy.symbols("a x b") + expr_01 = MathematicalExpression(a * x + b) + expr_01.set_parameter("a", 2) + expr_01.set_parameter("b", 3) + print(expr_01.parameters) + print(expr_01.get_variable_names()) + print(expr_01.evaluate(x=3)) + + tree = { + "equipment": equipment, + "data": measurement_data, + "measurements": measurements, + # "expression": expr_01, + "measurement_chains": measurement_chains, + "data_sources": sources, + "data_processors": processors, + } + + asdf_buffer = BytesIO() + + with asdf.AsdfFile(tree, extensions=[WeldxExtension(), WeldxAsdfExtension()]) as f: + f.write_to(asdf_buffer) + asdf_buffer.seek(0) + + asdf.open(asdf_buffer, extensions=[WeldxExtension(), WeldxAsdfExtension()]) diff --git a/tutorials/measurement_example.ipynb b/tutorials/measurement_example.ipynb new file mode 100644 index 000000000..c83b7a78b --- /dev/null +++ b/tutorials/measurement_example.ipynb @@ -0,0 +1,541 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "C:\\Python\\weldx\n" + ] + } + ], + "source": [ + "%cd .." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "import asdf\n", + "import pandas as pd\n", + "import pint\n", + "import sympy\n", + "import weldx\n", + "import weldx.measurement as msm\n", + "import weldx.transformations as tf\n", + "import xarray as xr\n", + "from matplotlib import pyplot as plt\n", + "from weldx import Q_\n", + "from weldx.asdf.extension import WeldxAsdfExtension, WeldxExtension" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Handling measurements\n", + "## Overview\n", + "In this short example we use welding voltage and current measurements to show how to describe and store measurements with associated measurement chains. This includes describing the measurement equipment and its metadata, describing all the relevant transformation steps from raw-data to the final output and the data itself. The final result is a `MeasurementChain` that should be easy to follow and represent the complete data processing pipeline." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generating the measurement data\n", + "We start by creating some \"dummy\" datasets that represent the current and voltage measurements.\n", + "In a real application, these would be the datasets that we would copy from our measurement equipment (e.g. downloaded form a HKS-WeldQAS, oscilloscope or similar systems).\n", + "The values in these dataset represent the actual physical current and voltage data in A and V." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# a small helper function to generate a sine signal with its time axis. This supports using pint.Quantities !\n", + "def sine(f, amp, t_end, samples=None):\n", + " \"\"\"Generate a sine with frequency f, amplitude amp and timespan t_end\"\"\"\n", + " if samples is None: # default to 50 samples per period\n", + " samples = int((t_end * 2 * Q_(\"Hz\") * 50).to_reduced_units()) + 1\n", + " om = 2 * np.pi * f\n", + " t = np.linspace(0 * Q_(\"s\"), t_end, num=samples)\n", + " y = amp * np.sin(t * om)\n", + " return y, t" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Data(name='Welding current', data=\n", + "\n", + "Coordinates:\n", + " * time (time) datetime64[ns] 2020-06-01 ... 2020-06-01T00:00:10)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "I, time = sine(f=1.5 * Q_(\"Hz\"), amp=50 * Q_(\"A\"), t_end=10 * Q_(\"s\"))\n", + "time = pd.TimedeltaIndex(data=time.magnitude, unit=\"s\") + pd.Timestamp(\"2020-06-01\")\n", + "\n", + "current_data = msm.Data(\n", + " name=\"Welding current\", data=xr.DataArray(I, dims=[\"time\"], coords={\"time\": time})\n", + ")\n", + "current_data" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Data(name='Welding voltage', data=\n", + "\n", + "Coordinates:\n", + " * time (time) datetime64[ns] 2020-06-01 ... 2020-06-01T00:00:10)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "V, time = sine(f=1.5 * Q_(\"Hz\"), amp=20 * Q_(\"V\"), t_end=10 * Q_(\"s\"))\n", + "time = pd.TimedeltaIndex(data=time.magnitude, unit=\"s\") + pd.Timestamp(\"2020-06-01\")\n", + "\n", + "voltage_data = msm.Data(\n", + " name=\"Welding voltage\", data=xr.DataArray(V, dims=[\"time\"], coords={\"time\": time})\n", + ")\n", + "voltage_data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is important to note the type and structure of the `current_data` and `voltage_data` datasets:\n", + "- they are created as `xarray.DataArrays`\n", + "- the data itself is a `pint.Quantity` i.e. a numpy array with associated unit. For the current measurement this is `ampere`, the voltage is given in `volt`. Using quantities is an important core concept of measurements !\n", + "- each `DataArray` has a `time` dimension and coordinate using numpy datetime formats." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Equipment and Software\n", + "Next, let's define some of the equipment and software that is used throughout the measurement chain. We will use and add more information to these objects later.\n", + "\n", + "In out example, two types of hardware equipment are used:\n", + "- The [`HKS P1000-S3`](https://hks-prozesstechnik.de/en/sensors-2/) is a standard welding process sensor that detects the welding voltage and current using a hall sensor. The result is output as two analog signals scaled to +/- 10 V.\n", + "- The [`Beckhoff ELM3002-0000`](https://www.beckhoff.com/ELM3002/) is a fieldbus AD-converter terminal that picks up the analog signals of the HKS Sensor and transmits them digitally to the control software.\n", + "\n", + "The final piece involved in the measurement chain is the Software used to record, scale and save both the welding current and voltage measurements. We define the software version and name used during the example using built-in ASDF types." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "HKS_sensor = msm.GenericEquipment(name=\"HKS P1000-S3\")\n", + "\n", + "BH_ELM = msm.GenericEquipment(name=\"Beckhoff ELM3002-0000\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "from asdf.tags.core import Software\n", + "\n", + "twincat_scope = Software(name=\"Beckhoff TwinCAT ScopeView\", version=\"3.4.3143\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Defining a measurement chain: current measurement\n", + "Now we define the missing elements of our measurement chain and bundle everything together. A core concept of the chain are `signals` that go in and out of `transformations` which define mathematical operations of the signals, forming the chain.\n", + "\n", + "Each measurement chain starts with a `Source` signal. This is the point where our physical process or quantity is initially detected by a sensor. We define a `measurement.Source` object by giving a `name`, defining the `Signal` type and an `error` representing the uncertainty attached to the signal source. In a way, a `Source` is a special type of `transformation` in that it only has an `output_signal` but no `input_signal`.\n", + "\n", + "For our current measurement, the source outputs an **analog Signal** of unit **V**. Since there is no recording of this measurement we do not provide the data. According to the spec sheet of the sensor the measurement error in this initial step is **0.1 %** which can be documented using the `Error` property (again using quantities)." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "src_current = msm.Source(\n", + " name=\"Current Sensor\",\n", + " output_signal=msm.Signal(signal_type=\"analog\", unit=\"V\", data=None),\n", + " error=msm.Error(Q_(0.1, \"percent\")),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We associate the current measurement source with the HKS sensor by adding it to its list of sources. We now have the start of our measurement chain defined." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "HKS_sensor.sources = []\n", + "HKS_sensor.sources.append(src_current)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The next step in the chain is picking up the analog voltage signal from our source with the Beckhoff AD converter terminal which transform the signal into an internal signed integer value. The formula describing this linear transformation with input `x` is\n", + "```\n", + "a * x + b\n", + "32768 / (10 V) * x + 0\n", + "```\n", + "We express this signal transformation as an analytical formula created with the `sympy` package. Based on the above formula we also define the static parameters `a` and `b` in the `MathematicalExpression`. Note that we use quantities here as well !\n", + "Since our result is a dimensionless integer `a` has the unit **1/V** and `b` is dimensionless which we indicate with `\"\"`." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "from weldx.asdf.tags.weldx.core.mathematical_expression import MathematicalExpression\n", + "\n", + "[a, x, b] = sympy.symbols(\"a x b\")\n", + "current_AD_func = MathematicalExpression(a * x + b)\n", + "current_AD_func.set_parameter(\"a\", Q_(32768.0 / 10.0, \"1/V\"))\n", + "current_AD_func.set_parameter(\"b\", Q_(0.0, \"\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we have the transform function we can define our `DataTransformation`. The `input_signal` of our transformation is the `src_current.output_signal` object from our source signal defined earlier.\n", + "Our new Transformation outputs a new **dimensionless** signal of type **digital**. Once again, since we have no data record of this we do not assign any data object to the signal. The Beckhoff AD converter lists the measurement Error at **0.01 %**." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "current_AD_transform = msm.DataTransformation(\n", + " name=\"AD conversion current measurement\",\n", + " input_signal=src_current.output_signal,\n", + " output_signal=msm.Signal(\"digital\", \"\", data=None),\n", + " error=msm.Error(Q_(0.01, \"percent\")),\n", + " func=current_AD_func,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We also associate the transformation to the Beckhoff equipment:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "BH_ELM.data_transformations = []\n", + "BH_ELM.data_transformations.append(current_AD_transform)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similar to the AD conversion, we add the final step of our signal processing chain: digitally converting the signal to the final physical representation of the welding current. The current calibration formula from our integer values to the real current values is as follows:\n", + "```\n", + "1000 A / 32768 * x + 0 A\n", + "```\n", + "Put into a new `sympy` expression:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# define current output calibration expression and transformation\n", + "current_calib_func = MathematicalExpression(a * x + b)\n", + "current_calib_func.set_parameter(\"a\", Q_(1000.0 / 32768.0, \"A\"))\n", + "current_calib_func.set_parameter(\"b\", Q_(0.0, \"A\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We create the final transformation step:\n", + "- the input signal now is the output signal of the previous AD conversion\n", + "- the output signal is our final current measurement representation\n", + "- we add the our measurement data to this signal !\n", + "- we add the software as a meta field to the signal transformation" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "current_calib_transform = msm.DataTransformation(\n", + " name=\"Calibration current measurement\",\n", + " input_signal=current_AD_transform.output_signal,\n", + " output_signal=msm.Signal(\"digital\", \"A\", data=current_data),\n", + " error=msm.Error(0.0),\n", + " func=current_calib_func,\n", + " meta=twincat_scope,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that all transformation steps are defined, we can create our `MeasurementChain` object. We pass on the initial source as well as all transformation steps in order." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "welding_current_chain = msm.MeasurementChain(\n", + " name=\"welding current measurement chain\",\n", + " data_source=src_current,\n", + " data_processors=[current_AD_transform, current_calib_transform],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally the `Measurement` is our measurement chain with another link to the data." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "welding_current = msm.Measurement(\n", + " name=\"welding current measurement\",\n", + " data=[current_data],\n", + " measurement_chain=welding_current_chain,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## voltage measurement\n", + "We follow the same procedure described in the current measurement here :-)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "src_voltage = msm.Source(\n", + " name=\"Voltage Sensor\",\n", + " output_signal=msm.Signal(\"analog\", \"V\", data=None),\n", + " error=msm.Error(Q_(0.1, \"percent\")),\n", + ")\n", + "\n", + "HKS_sensor.sources.append(src_voltage)\n", + "\n", + "# define AD conversion expression and transformation step\n", + "[a, x, b] = sympy.symbols(\"a x b\")\n", + "voltage_ad_func = MathematicalExpression(a * x + b)\n", + "voltage_ad_func.set_parameter(\"a\", Q_(32768.0 / 10.0, \"1/V\"))\n", + "voltage_ad_func.set_parameter(\"b\", Q_(0.0, \"\"))\n", + "\n", + "voltage_AD_transform = msm.DataTransformation(\n", + " name=\"AD conversion voltage measurement\",\n", + " input_signal=src_voltage.output_signal,\n", + " output_signal=msm.Signal(\"digital\", \"\", data=None),\n", + " error=msm.Error(Q_(0.01, \"percent\")),\n", + " func=voltage_ad_func,\n", + ")\n", + "\n", + "HKS_sensor.data_transformations.append(voltage_AD_transform)\n", + "\n", + "# define voltage output calibration expression and transformation\n", + "voltage_calib_func = MathematicalExpression(a * x + b)\n", + "voltage_calib_func.set_parameter(\"a\", Q_(100.0 / 32768.0, \"V\"))\n", + "voltage_calib_func.set_parameter(\"b\", Q_(0.0, \"V\"))\n", + "\n", + "voltage_calib_transform = msm.DataTransformation(\n", + " name=\"Calibration voltage measurement\",\n", + " input_signal=voltage_AD_transform.output_signal,\n", + " output_signal=msm.Signal(\"digital\", \"V\", data=voltage_data),\n", + " error=msm.Error(0.0),\n", + " func=voltage_calib_func,\n", + " meta=twincat_scope,\n", + ")\n", + "\n", + "\n", + "welding_voltage_chain = msm.MeasurementChain(\n", + " name=\"welding voltage measurement chain\",\n", + " data_source=src_voltage,\n", + " data_processors=[voltage_AD_transform, voltage_calib_transform],\n", + ")\n", + "\n", + "welding_voltage = msm.Measurement(\n", + " name=\"welding voltage measurement\",\n", + " data=[voltage_data],\n", + " measurement_chain=welding_voltage_chain,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Coordinate Systems\n", + "\n", + "Most data does not make much sense without being able to determine where it was recorded in relation to a specimen or other measurement spots. Therefore, we define coordinate systems and their orientations towards each other. The basic principles are already explained in the transformation tutorials, so we will just define some coordinate systems without further explanation. To keep things simple, no time dependent coordinates are considered. " + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "lcs_specimen_in_root = tf.LocalCoordinateSystem(\n", + " coordinates=Q_(np.asarray([100, 75, 0]), \"mm\")\n", + ")\n", + "lcs_flange_in_root = tf.LocalCoordinateSystem(\n", + " orientation=tf.rotation_matrix_x(np.pi / 2),\n", + " coordinates=Q_(np.asarray([115, -10, 140]), \"mm\"),\n", + ")\n", + "lcs_torch_in_flange = tf.LocalCoordinateSystem(\n", + " coordinates=Q_(np.asarray([100, 75, 0]), \"mm\")\n", + ")\n", + "\n", + "coordinate_systems = tf.CoordinateSystemManager(\"root\")\n", + "coordinate_systems.add_coordinate_system(\"specimen\", \"root\", lcs_specimen_in_root)\n", + "coordinate_systems.add_coordinate_system(\"flange\", \"root\", lcs_flange_in_root)\n", + "coordinate_systems.add_coordinate_system(\"torch\", \"flange\", lcs_torch_in_flange)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> **TODO:** Connect data to coordinate systems" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Writing to ASDF\n", + "Once we have define all object we can write them to a ASDF file. To make the file easier to read we place some elements earlier in the tree." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "equipment = [HKS_sensor, BH_ELM]\n", + "measurement_data = [current_data, voltage_data]\n", + "measurements = [welding_current, welding_voltage]\n", + "\n", + "tree = {\n", + " \"coordinate_systems\": coordinate_systems,\n", + " \"equipment\": equipment,\n", + " \"data\": measurement_data,\n", + " \"measurements\": measurements,\n", + " # \"expression\": expr_01,\n", + " # \"measurement_chains\": measurement_chains,\n", + " # \"data_sources\": sources,\n", + " # \"data_processors\": processors,\n", + "}\n", + "with asdf.AsdfFile(tree, extensions=[WeldxExtension(), WeldxAsdfExtension()]) as f:\n", + " f.write_to(\"tutorials/measurement_example.asdf\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "weldx", + "language": "python", + "name": "weldx" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/weldx/__init__.py b/weldx/__init__.py index f1fddf077..fb9e94d5a 100644 --- a/weldx/__init__.py +++ b/weldx/__init__.py @@ -2,7 +2,6 @@ # asdf extensions and tags import weldx.asdf - # geometry packages import weldx.geometry import weldx.transformations @@ -19,4 +18,4 @@ __version__ = get_versions()["version"] del get_versions -__all__ = ["geometry", "transformations", "utility", "asdf", "Q_"] +__all__ = ["geometry", "measurement", "transformations", "utility", "asdf", "Q_"] diff --git a/weldx/asdf/schemas/weldx.bam.de/weldx/core/mathematical_expression-1.0.0.yaml b/weldx/asdf/schemas/weldx.bam.de/weldx/core/mathematical_expression-1.0.0.yaml new file mode 100644 index 000000000..71e5fc8b4 --- /dev/null +++ b/weldx/asdf/schemas/weldx.bam.de/weldx/core/mathematical_expression-1.0.0.yaml @@ -0,0 +1,25 @@ +%YAML 1.1 +--- +$schema: "http://stsci.edu/schemas/yaml-schema/draft-01" +id: "http://weldx.bam.de/schemas/weldx/core/mathematical_expression-1.0.0" +tag: "tag:weldx.bam.de:weldx/core/mathematical_expression-1.0.0" + +title: | + Schema that describes a mathematical_expression. +description: | + Defines a mathematical expression using sympy syntax. + https://docs.sympy.org/latest/modules/core.html#module-sympy.core.sympify + + Also contains any predefined (constant) parameters used in the expression. +type: object +properties: + expression: + type: string + parameters: + type: object + +required: [expression] +propertyOrder: [expression, parameters] +flowStyle: block +additionalProperties: false +... \ No newline at end of file diff --git a/weldx/asdf/schemas/weldx.bam.de/weldx/debug/validator_testclass-1.0.0.yaml b/weldx/asdf/schemas/weldx.bam.de/weldx/debug/validator_testclass-1.0.0.yaml index 0ae01a8ad..b2a5cef80 100644 --- a/weldx/asdf/schemas/weldx.bam.de/weldx/debug/validator_testclass-1.0.0.yaml +++ b/weldx/asdf/schemas/weldx.bam.de/weldx/debug/validator_testclass-1.0.0.yaml @@ -17,7 +17,10 @@ properties: velocity_prop: description: | a simple velocity quantity - $ref: tag:stsci.edu:asdf/unit/quantity-1.1.0 + allOf: + - $ref: tag:stsci.edu:asdf/unit/quantity-1.1.0 + - type: object + wx_unit: "m/s" current_prop: description: | diff --git a/weldx/asdf/schemas/weldx.bam.de/weldx/equipment/generic_equipment-1.0.0.yaml b/weldx/asdf/schemas/weldx.bam.de/weldx/equipment/generic_equipment-1.0.0.yaml new file mode 100644 index 000000000..f9b9929c8 --- /dev/null +++ b/weldx/asdf/schemas/weldx.bam.de/weldx/equipment/generic_equipment-1.0.0.yaml @@ -0,0 +1,30 @@ +%YAML 1.1 +--- +$schema: "http://stsci.edu/schemas/yaml-schema/draft-01" +id: "http://weldx.bam.de/schemas/weldx/equipment/generic_equipment-1.0.0" +tag: "tag:weldx.bam.de:weldx/equipment/generic_equipment-1.0.0" + +title: | + A generic piece of equipment. +description: | + Generic placeholder class do describe any kind of equipment with additional metadata. + Equipments can be associated with signal sources and data transformations. +type: object +properties: + name: + type: string + sources: + type: array + items: + $ref: "tag:weldx.bam.de:weldx/measurement/source-1.0.0" + data_transformations: + type: array + items: + $ref: "tag:weldx.bam.de:weldx/measurement/data_transformation-1.0.0" + +propertyOrder: [name, sources, data_transformations] +required: [name] + +flowStyle: block +additionalProperties: false +... \ No newline at end of file diff --git a/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/data-1.0.0.yaml b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/data-1.0.0.yaml new file mode 100644 index 000000000..f62a921c0 --- /dev/null +++ b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/data-1.0.0.yaml @@ -0,0 +1,24 @@ +%YAML 1.1 +--- +$schema: "http://stsci.edu/schemas/yaml-schema/draft-01" +id: "http://weldx.bam.de/schemas/weldx/measurement/data-1.0.0" +tag: "tag:weldx.bam.de:weldx/measurement/data-1.0.0" + +title: | + Data container for measurement recordings. +description: | + Data is stored using data_array format. + TODO: rework schema, add time axes requirement +type: object +properties: + name: + type: string + data: + $ref: "tag:weldx.bam.de:weldx/core/data_array-1.0.0" + +propertyOrder: [name, data] +required: [data] + +flowStyle: block +additionalProperties: false +... \ No newline at end of file diff --git a/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/data_transformation-1.0.0.yaml b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/data_transformation-1.0.0.yaml new file mode 100644 index 000000000..7cd7c2caf --- /dev/null +++ b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/data_transformation-1.0.0.yaml @@ -0,0 +1,30 @@ +%YAML 1.1 +--- +$schema: "http://stsci.edu/schemas/yaml-schema/draft-01" +id: "http://weldx.bam.de/schemas/weldx/measurement/data_transformation-1.0.0" +tag: "tag:weldx.bam.de:weldx/measurement/data_transformation-1.0.0" + +title: | + Signal transformation step. +description: | + Describes the transformation between an input and output signal. + Optionally describes the associated error of the transformation step and the mathematical formula. +type: object +properties: + name: + type: string + input_signal: + $ref: "tag:weldx.bam.de:weldx/measurement/signal-1.0.0" + output_signal: + $ref: "tag:weldx.bam.de:weldx/measurement/signal-1.0.0" + error: + $ref: "tag:weldx.bam.de:weldx/measurement/error-1.0.0" + func: + $ref: "tag:weldx.bam.de:weldx/core/mathematical_expression-1.0.0" + +required: [name, input_signal, output_signal] +propertyOrder: [name, input_signal, output_signal, error, func] + +flowStyle: block +additionalProperties: false +... \ No newline at end of file diff --git a/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/error-1.0.0.yaml b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/error-1.0.0.yaml new file mode 100644 index 000000000..75f26717f --- /dev/null +++ b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/error-1.0.0.yaml @@ -0,0 +1,25 @@ +%YAML 1.1 +--- +$schema: "http://stsci.edu/schemas/yaml-schema/draft-01" +id: "http://weldx.bam.de/schemas/weldx/measurement/error-1.0.0" +tag: "tag:weldx.bam.de:weldx/measurement/error-1.0.0" + +title: | + Measurement error description. +description: | + A basic schema to represent a measurement error. +type: object +properties: + deviation: + description: | + A simple numerical representation of the error. + anyOf: + - type: number + - $ref: tag:stsci.edu:asdf/unit/quantity-1.1.0 + + +required: [deviation] + +flowStyle: block +additionalProperties: false +... \ No newline at end of file diff --git a/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/measurement-1.0.0.yaml b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/measurement-1.0.0.yaml new file mode 100644 index 000000000..b023fdd27 --- /dev/null +++ b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/measurement-1.0.0.yaml @@ -0,0 +1,27 @@ +%YAML 1.1 +--- +$schema: "http://stsci.edu/schemas/yaml-schema/draft-01" +id: "http://weldx.bam.de/schemas/weldx/measurement/measurement-1.0.0" +tag: "tag:weldx.bam.de:weldx/measurement/measurement-1.0.0" + +title: | + Schema that describes a measurement +type: object +properties: + name: + type: string + data: + type: array + items: + $ref: "tag:weldx.bam.de:weldx/measurement/data-1.0.0" + measurement_chain: + $ref: "tag:weldx.bam.de:weldx/measurement/measurement_chain-1.0.0" + + +propertyOrder: [name, data, measurement_chain] +required: [name, data] + + + +flowStyle: block +additionalProperties: false \ No newline at end of file diff --git a/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/measurement_chain-1.0.0.yaml b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/measurement_chain-1.0.0.yaml new file mode 100644 index 000000000..ad7cd9712 --- /dev/null +++ b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/measurement_chain-1.0.0.yaml @@ -0,0 +1,27 @@ +%YAML 1.1 +--- +$schema: "http://stsci.edu/schemas/yaml-schema/draft-01" +id: "http://weldx.bam.de/schemas/weldx/measurement/measurement_chain-1.0.0" +tag: "tag:weldx.bam.de:weldx/measurement/measurement_chain-1.0.0" + +title: | + Schema that describes a measurement_chain +type: object +properties: + name: + type: string + data_source: + $ref: "tag:weldx.bam.de:weldx/measurement/source-1.0.0" + data_processors: + type: array + items: + $ref: "tag:weldx.bam.de:weldx/measurement/data_transformation-1.0.0" + + +propertyOrder: [name, data_source, data_processors] +required: [name, data_source] + + + +flowStyle: block +additionalProperties: false \ No newline at end of file diff --git a/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/signal-1.0.0.yaml b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/signal-1.0.0.yaml new file mode 100644 index 000000000..6e8d3ed8f --- /dev/null +++ b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/signal-1.0.0.yaml @@ -0,0 +1,24 @@ +%YAML 1.1 +--- +$schema: "http://stsci.edu/schemas/yaml-schema/draft-01" +id: "http://weldx.bam.de/schemas/weldx/measurement/signal-1.0.0" +tag: "tag:weldx.bam.de:weldx/measurement/signal-1.0.0" + +title: | + Representation of a measurement signal with optional data attached. +type: object +properties: + signal_type: + type: string + unit: + type: string + data: + $ref: "tag:weldx.bam.de:weldx/measurement/data-1.0.0" + + +propertyOrder: [signal_type, unit, data] +required: [signal_type, unit] + +flowStyle: block +additionalProperties: false +... \ No newline at end of file diff --git a/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/source-1.0.0.yaml b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/source-1.0.0.yaml new file mode 100644 index 000000000..c27c7c01a --- /dev/null +++ b/weldx/asdf/schemas/weldx.bam.de/weldx/measurement/source-1.0.0.yaml @@ -0,0 +1,25 @@ +%YAML 1.1 +--- +$schema: "http://stsci.edu/schemas/yaml-schema/draft-01" +id: "http://weldx.bam.de/schemas/weldx/measurement/source-1.0.0" +tag: "tag:weldx.bam.de:weldx/measurement/source-1.0.0" + +title: | + Schema that describes a measurement source. +type: object +properties: + name: + type: string + output_signal: + $ref: "tag:weldx.bam.de:weldx/measurement/signal-1.0.0" + error: + $ref: "tag:weldx.bam.de:weldx/measurement/error-1.0.0" + +required: [name, output_signal] +propertyOrder: [name, output_signal, error] + + + +flowStyle: block +additionalProperties: false +... \ No newline at end of file diff --git a/weldx/asdf/tags/weldx/__init__.py b/weldx/asdf/tags/weldx/__init__.py index d74a4fa94..55e5c1fc5 100644 --- a/weldx/asdf/tags/weldx/__init__.py +++ b/weldx/asdf/tags/weldx/__init__.py @@ -1 +1 @@ -from . import aws, core, debug, time, unit +from . import aws, core, debug, equipment, measurement, time, unit diff --git a/weldx/asdf/tags/weldx/core/__init__.py b/weldx/asdf/tags/weldx/core/__init__.py index 5836ac419..47e23e5d9 100644 --- a/weldx/asdf/tags/weldx/core/__init__.py +++ b/weldx/asdf/tags/weldx/core/__init__.py @@ -1 +1,8 @@ -from . import common_types, data_array, dataset, iso_groove, transformations +from . import ( + common_types, + data_array, + dataset, + iso_groove, + mathematical_expression, + transformations, +) diff --git a/weldx/asdf/tags/weldx/core/mathematical_expression.py b/weldx/asdf/tags/weldx/core/mathematical_expression.py new file mode 100644 index 000000000..a42a52f52 --- /dev/null +++ b/weldx/asdf/tags/weldx/core/mathematical_expression.py @@ -0,0 +1,89 @@ +import sympy + +from weldx.asdf.types import WeldxType + +__all__ = ["MathematicalExpression", "MathematicalExpressionType"] + + +class MathematicalExpression: + """Mathematical expression using sympy syntax.""" + + def __init__(self, expression: sympy.core.basic.Basic): + """Initialize a MathematicalExpression from sympy objects. + + Parameters + ---------- + expression + sympy object that can be turned into an expression. + E.g. any valid combination of previously defined sympy.symbols . + """ + self.expression = expression + self.function = sympy.lambdify( + self.expression.free_symbols, self.expression, "numpy" + ) + self.parameters = {} + + def set_parameter(self, name, value): + """Define an expression parameter as constant value. + + Parameters + ---------- + name + Name of the parameter used in the expression. + value + Parameter value. This can be number, array or pint.Quantity + + """ + self.parameters[name] = value + + def get_variable_names(self): + """Get a list of all expression variables. + + Returns + ------- + List + + """ + variable_names = [] + for var in self.expression.free_symbols: + if var.__str__() not in self.parameters: + variable_names.append(var.__str__()) + return variable_names + + def evaluate(self, **kwargs): + """Evaluate the expression for specific variable values. + + Parameters + ---------- + kwargs + additional keyword arguments (variable assignment) to pass. + + Returns + ------- + float + + """ + inputs = {**kwargs, **self.parameters} + return self.function(**inputs) + + +class MathematicalExpressionType(WeldxType): + """Serialization class for sympy style math expressions.""" + + name = "core/mathematical_expression" + version = "1.0.0" + types = [MathematicalExpression] + requires = ["weldx"] + handle_dynamic_subclasses = True + + @classmethod + def to_tree(cls, node: MathematicalExpression, ctx): + """convert to tagged tree and remove all None entries from node dictionary""" + tree = {"expression": node.expression.__str__(), "parameters": node.parameters} + return tree + + @classmethod + def from_tree(cls, tree, ctx): + obj = MathematicalExpression(sympy.sympify(tree["expression"])) + obj.parameters = tree["parameters"] + return obj diff --git a/weldx/asdf/tags/weldx/core/transformations/coordinate_system_hierarchy.py b/weldx/asdf/tags/weldx/core/transformations/coordinate_system_hierarchy.py index 1fae757e6..3233964ca 100644 --- a/weldx/asdf/tags/weldx/core/transformations/coordinate_system_hierarchy.py +++ b/weldx/asdf/tags/weldx/core/transformations/coordinate_system_hierarchy.py @@ -170,7 +170,7 @@ def from_tree(cls, tree, ctx): for cs_data in coordinate_systems: if not csm.has_coordinate_system(cs_data.name): if csm.has_coordinate_system(cs_data.reference_system): - csm.add_coordinate_system( + csm.add_cs( cs_data.name, cs_data.reference_system, cs_data.transformation, diff --git a/weldx/asdf/tags/weldx/debug/const_class.py b/weldx/asdf/tags/weldx/debug/const_class.py index 7d08dccc9..baced0447 100644 --- a/weldx/asdf/tags/weldx/debug/const_class.py +++ b/weldx/asdf/tags/weldx/debug/const_class.py @@ -16,7 +16,7 @@ class ConstClass: class ConstClassType(WeldxType): - """""" + """Serialization testclass for const validator.""" name = "debug/const_class" version = "1.0.0" diff --git a/weldx/asdf/tags/weldx/debug/validator_testclass.py b/weldx/asdf/tags/weldx/debug/validator_testclass.py index 838939a68..fc1cd9933 100644 --- a/weldx/asdf/tags/weldx/debug/validator_testclass.py +++ b/weldx/asdf/tags/weldx/debug/validator_testclass.py @@ -22,7 +22,7 @@ class ValidatorTestClass: class ValidatorTestClassType(WeldxType): - """""" + """Serialization testclass custom validators.""" name = "debug/validator_testclass" version = "1.0.0" diff --git a/weldx/asdf/tags/weldx/equipment/__init__.py b/weldx/asdf/tags/weldx/equipment/__init__.py new file mode 100644 index 000000000..fa1534cb8 --- /dev/null +++ b/weldx/asdf/tags/weldx/equipment/__init__.py @@ -0,0 +1 @@ +from . import generic_equipment diff --git a/weldx/asdf/tags/weldx/equipment/generic_equipment.py b/weldx/asdf/tags/weldx/equipment/generic_equipment.py new file mode 100644 index 000000000..6cc179d5b --- /dev/null +++ b/weldx/asdf/tags/weldx/equipment/generic_equipment.py @@ -0,0 +1,28 @@ +from weldx.asdf.types import WeldxType +from weldx.asdf.utils import drop_none_attr +from weldx.measurement import GenericEquipment + +__all__ = ["GenericEquipment", "GenericEquipmentType"] + + +class GenericEquipmentType(WeldxType): + """Serialization class for generic-equipment.""" + + name = "equipment/generic_equipment" + version = "1.0.0" + types = [GenericEquipment] + requires = ["weldx"] + handle_dynamic_subclasses = True + + @classmethod + def to_tree(cls, node: GenericEquipment, ctx): + """convert to tagged tree and remove all None entries from node dictionary""" + tree = drop_none_attr(node) + return tree + + @classmethod + def from_tree(cls, tree, ctx): + if "sources" not in tree: + tree["sources"] = None + obj = GenericEquipment(**tree) + return obj diff --git a/weldx/asdf/tags/weldx/measurement/__init__.py b/weldx/asdf/tags/weldx/measurement/__init__.py new file mode 100644 index 000000000..a4075c7ca --- /dev/null +++ b/weldx/asdf/tags/weldx/measurement/__init__.py @@ -0,0 +1,9 @@ +from . import ( + data, + data_transformation, + error, + measurement, + measurement_chain, + signal, + source, +) diff --git a/weldx/asdf/tags/weldx/measurement/data.py b/weldx/asdf/tags/weldx/measurement/data.py new file mode 100644 index 000000000..33c92444a --- /dev/null +++ b/weldx/asdf/tags/weldx/measurement/data.py @@ -0,0 +1,26 @@ +from weldx.asdf.types import WeldxType +from weldx.asdf.utils import drop_none_attr +from weldx.measurement import Data + +__all__ = ["Data", "DataType"] + + +class DataType(WeldxType): + """Serialization class measurement data.""" + + name = "measurement/data" + version = "1.0.0" + types = [Data] + requires = ["weldx"] + handle_dynamic_subclasses = True + + @classmethod + def to_tree(cls, node: Data, ctx): + """convert to tagged tree and remove all None entries from node dictionary""" + tree = drop_none_attr(node) + return tree + + @classmethod + def from_tree(cls, tree, ctx): + obj = Data(**tree) + return obj diff --git a/weldx/asdf/tags/weldx/measurement/data_transformation.py b/weldx/asdf/tags/weldx/measurement/data_transformation.py new file mode 100644 index 000000000..0d7865624 --- /dev/null +++ b/weldx/asdf/tags/weldx/measurement/data_transformation.py @@ -0,0 +1,26 @@ +from weldx.asdf.types import WeldxType +from weldx.asdf.utils import drop_none_attr +from weldx.measurement import DataTransformation + +__all__ = ["DataTransformation", "DataTransformationType"] + + +class DataTransformationType(WeldxType): + """Serialization class for data transformations.""" + + name = "measurement/data_transformation" + version = "1.0.0" + types = [DataTransformation] + requires = ["weldx"] + handle_dynamic_subclasses = True + + @classmethod + def to_tree(cls, node: DataTransformation, ctx): + """convert to tagged tree and remove all None entries from node dictionary""" + tree = drop_none_attr(node) + return tree + + @classmethod + def from_tree(cls, tree, ctx): + obj = DataTransformation(**tree) + return obj diff --git a/weldx/asdf/tags/weldx/measurement/error.py b/weldx/asdf/tags/weldx/measurement/error.py new file mode 100644 index 000000000..5c8174753 --- /dev/null +++ b/weldx/asdf/tags/weldx/measurement/error.py @@ -0,0 +1,26 @@ +from weldx.asdf.types import WeldxType +from weldx.asdf.utils import drop_none_attr +from weldx.measurement import Error + +__all__ = ["Error", "ErrorType"] + + +class ErrorType(WeldxType): + """Serialization class for measurement errors.""" + + name = "measurement/error" + version = "1.0.0" + types = [Error] + requires = ["weldx"] + handle_dynamic_subclasses = True + + @classmethod + def to_tree(cls, node: Error, ctx): + """convert to tagged tree and remove all None entries from node dictionary""" + tree = drop_none_attr(node) + return tree + + @classmethod + def from_tree(cls, tree, ctx): + obj = Error(**tree) + return obj diff --git a/weldx/asdf/tags/weldx/measurement/measurement.py b/weldx/asdf/tags/weldx/measurement/measurement.py new file mode 100644 index 000000000..fe5133def --- /dev/null +++ b/weldx/asdf/tags/weldx/measurement/measurement.py @@ -0,0 +1,26 @@ +from weldx.asdf.types import WeldxType +from weldx.asdf.utils import drop_none_attr +from weldx.measurement import Measurement + +__all__ = ["Measurement", "MeasurementType"] + + +class MeasurementType(WeldxType): + """Serialization class for measurement objects.""" + + name = "measurement/measurement" + version = "1.0.0" + types = [Measurement] + requires = ["weldx"] + handle_dynamic_subclasses = True + + @classmethod + def to_tree(cls, node: Measurement, ctx): + """convert to tagged tree and remove all None entries from node dictionary""" + tree = drop_none_attr(node) + return tree + + @classmethod + def from_tree(cls, tree, ctx): + obj = Measurement(**tree) + return obj diff --git a/weldx/asdf/tags/weldx/measurement/measurement_chain.py b/weldx/asdf/tags/weldx/measurement/measurement_chain.py new file mode 100644 index 000000000..a137399ab --- /dev/null +++ b/weldx/asdf/tags/weldx/measurement/measurement_chain.py @@ -0,0 +1,26 @@ +from weldx.asdf.types import WeldxType +from weldx.asdf.utils import drop_none_attr +from weldx.measurement import MeasurementChain + +__all__ = ["MeasurementChain", "MeasurementChainType"] + + +class MeasurementChainType(WeldxType): + """Serialization class for measurement chains""" + + name = "measurement/measurement_chain" + version = "1.0.0" + types = [MeasurementChain] + requires = ["weldx"] + handle_dynamic_subclasses = True + + @classmethod + def to_tree(cls, node: MeasurementChain, ctx): + """convert to tagged tree and remove all None entries from node dictionary""" + tree = drop_none_attr(node) + return tree + + @classmethod + def from_tree(cls, tree, ctx): + obj = MeasurementChain(**tree) + return obj diff --git a/weldx/asdf/tags/weldx/measurement/signal.py b/weldx/asdf/tags/weldx/measurement/signal.py new file mode 100644 index 000000000..954c66ddc --- /dev/null +++ b/weldx/asdf/tags/weldx/measurement/signal.py @@ -0,0 +1,28 @@ +from weldx.asdf.types import WeldxType +from weldx.asdf.utils import drop_none_attr +from weldx.measurement import Signal + +__all__ = ["Signal", "SignalType"] + + +class SignalType(WeldxType): + """Serialization class for measurement signals.""" + + name = "measurement/signal" + version = "1.0.0" + types = [Signal] + requires = ["weldx"] + handle_dynamic_subclasses = True + + @classmethod + def to_tree(cls, node: Signal, ctx): + """convert to tagged tree and remove all None entries from node dictionary""" + tree = drop_none_attr(node) + return tree + + @classmethod + def from_tree(cls, tree, ctx): + if "data" not in tree: + tree["data"] = None + obj = Signal(**tree) + return obj diff --git a/weldx/asdf/tags/weldx/measurement/source.py b/weldx/asdf/tags/weldx/measurement/source.py new file mode 100644 index 000000000..a3c0c538c --- /dev/null +++ b/weldx/asdf/tags/weldx/measurement/source.py @@ -0,0 +1,26 @@ +from weldx.asdf.types import WeldxType +from weldx.asdf.utils import drop_none_attr +from weldx.measurement import Source + +__all__ = ["Source", "SourceType"] + + +class SourceType(WeldxType): + """Serialization class for measurement sources.""" + + name = "measurement/source" + version = "1.0.0" + types = [Source] + requires = ["weldx"] + handle_dynamic_subclasses = True + + @classmethod + def to_tree(cls, node: Source, ctx): + """convert to tagged tree and remove all None entries from node dictionary""" + tree = drop_none_attr(node) + return tree + + @classmethod + def from_tree(cls, tree, ctx): + obj = Source(**tree) + return obj diff --git a/weldx/measurement.py b/weldx/measurement.py new file mode 100644 index 000000000..6501e9ac5 --- /dev/null +++ b/weldx/measurement.py @@ -0,0 +1,80 @@ +"""Contains measurement related classes and functions.""" + +from dataclasses import dataclass, field +from typing import List, Union # noqa: F401 + +import xarray as xr + + +# measurement -------------------------------------------------------------------------- +@dataclass +class Data: + """Simple dataclass implementation for measurement data.""" + + name: str + data: xr.DataArray + + +@dataclass +class Error: + """Simple dataclass implementation for signal transformation errors.""" + + deviation: float + + +@dataclass +class Signal: + """Simple dataclass implementation for measurement signals.""" + + signal_type: str + unit: str + data: Union[Data, None] + + +@dataclass +class DataTransformation: + """Simple dataclass implementation for signal transformations.""" + + name: str + input_signal: Signal + output_signal: Signal + error: Error + func: str = None + meta: str = None + + +@dataclass +class Source: + """Simple dataclass implementation for signal sources.""" + + name: str + output_signal: Signal + error: Error + + +@dataclass +class MeasurementChain: + """Simple dataclass implementation for measurement chains.""" + + name: str + data_source: Source + data_processors: List = field(default_factory=lambda: []) + + +@dataclass +class Measurement: + """Simple dataclass implementation for generic measurements.""" + + name: str + data: Data + measurement_chain: MeasurementChain + + +# equipment ---------------------------------------------------------------------------- +@dataclass +class GenericEquipment: + """Simple dataclass implementation for generic equipment.""" + + name: str + sources: List = field(default_factory=lambda: []) + data_transformations: List = field(default_factory=lambda: []) diff --git a/weldx/transformations.py b/weldx/transformations.py index 3cbfae587..3b1b0fec6 100644 --- a/weldx/transformations.py +++ b/weldx/transformations.py @@ -9,6 +9,7 @@ import networkx as nx import numpy as np import pandas as pd +import pint import xarray as xr from scipy.spatial.transform import Rotation as Rot @@ -373,7 +374,7 @@ def __init__( pass if not isinstance(coordinates, xr.DataArray): - if not isinstance(coordinates, np.ndarray): + if not isinstance(coordinates, (np.ndarray, pint.Quantity)): coordinates = np.array(coordinates) time_coordinates = None if coordinates.ndim == 2: