9. How to handle WelDX files#

In this notebook we will demonstrate how to create, read, and update ASDF files created by WelDX. All the needed functionality is contained in a single class named WeldxFile. We are going to show different modes of operation, like working with physical files on your harddrive, and in-memory files, both read-only and read-write mode.

9.1. Imports#

The WeldxFile class is being imported from the top-level of the weldx package.

from datetime import datetime

import numpy as np
from IPython.display import display

from weldx import WeldxFile

9.2. Basic operations#

Now we create our first file, by invoking the WeldxFile constructor without any additional arguments. By doing so, we create an in-memory file. This means, that your changes will be temporary until you write it to an actual file on your harddrive. The file_handle attribute will point to the actual underlying file. In this case it is the in-memory file or buffer as shown below.

file = WeldxFile()
file.file_handle
<_io.BytesIO at 0x7febe4c97b50>

Next we assign some dictionary like data to the file, by storing it some attribute name enclosed by square brackets. Then we look at the representation of the file header or contents. This will depend on the execution environment. In JupyterLab you will see an interactive tree like structure, which can be expanded and searched. The root of the tree is denoted as “root” followed by children created by the ASDF library “asdf_library” and “history”. We attached the additional child “some_data” with our assignment.

data = {"data_sets": {"first": np.random.random(100), "time": datetime.now()}}
file["some_data"] = data
file
#ASDF 1.0.0
#ASDF_STANDARD 1.5.0
%YAML 1.1
%TAG ! tag:stsci.edu:asdf/
--- !core/asdf-1.1.0
asdf_library: !core/software-1.0.0 {author: The ASDF Developers, homepage: 'http://github.com/asdf-format/asdf',
  name: asdf, version: 2.15.1}
history:
  extensions:
  - !core/extension_metadata-1.0.0
    extension_class: asdf.extension.BuiltinExtension
    software: !core/software-1.0.0 {name: asdf, version: 2.15.1}
some_data:
  data_sets:
    first: !core/ndarray-1.0.0
      data: []
      datatype: float64
      shape: [100]
    time: 2024-04-14 09:27:09.048219

Note, that here we are using some very common types, namely an NumPy array and a timestamp. For weldx specialized types like the coordinates system manager, (welding) measurements etc., the weldx package provides ASDF extensions to handle those types automatically during loading and saving ASDF data. You do not need to worry about them. If you try to save types, which cannot be handled by ASDF, you will trigger an error.

We could also have created the same structure in one step:

file = WeldxFile(tree=data, mode="rw")
file
#ASDF 1.0.0
#ASDF_STANDARD 1.5.0
%YAML 1.1
%TAG ! tag:stsci.edu:asdf/
--- !core/asdf-1.1.0
asdf_library: !core/software-1.0.0 {author: The ASDF Developers, homepage: 'http://github.com/asdf-format/asdf',
  name: asdf, version: 2.15.1}
history:
  extensions:
  - !core/extension_metadata-1.0.0
    extension_class: asdf.extension.BuiltinExtension
    software: !core/software-1.0.0 {name: asdf, version: 2.15.1}
data_sets:
  first: !core/ndarray-1.0.0
    data: []
    datatype: float64
    shape: [100]
  time: 2024-04-14 09:27:09.048219

You might have noticed, that we got a warning about the in-memory operation during showing the file in Jupyter. Now we have passed the additional argument mode=”rw”, which indicates, that we want to perform write operations just in memory, or alternatively to the passed physical file. So this warning went away.

We can use all dictionary operations on the data we like, e.g. update, assign, and delete items.

file["data_sets"]["second"] = {"data": np.random.random(100), "time": datetime.now()}

# delete the first data set again:
del file["data_sets"]["first"]
file
#ASDF 1.0.0
#ASDF_STANDARD 1.5.0
%YAML 1.1
%TAG ! tag:stsci.edu:asdf/
--- !core/asdf-1.1.0
asdf_library: !core/software-1.0.0 {author: The ASDF Developers, homepage: 'http://github.com/asdf-format/asdf',
  name: asdf, version: 2.15.1}
history:
  extensions:
  - !core/extension_metadata-1.0.0
    extension_class: asdf.extension.BuiltinExtension
    software: !core/software-1.0.0 {name: asdf, version: 2.15.1}
data_sets:
  second:
    data: !core/ndarray-1.0.0
      data: []
      datatype: float64
      shape: [100]
    time: 2024-04-14 09:27:09.135046
  time: 2024-04-14 09:27:09.048219

We can also iterate over all keys as usual. You can also have a look at the documentation of the builtin type dict for a complete overview of its features.

for key, value in file.items():
    print(key, value)
data_sets {'time': datetime.datetime(2024, 4, 14, 9, 27, 9, 48219), 'second': {'data': array([0.79793627, 0.57472844, 0.90132713, 0.44413594, 0.63851354,
       0.87542551, 0.15202641, 0.40396641, 0.80832456, 0.93028275,
       0.54981723, 0.13032565, 0.66825968, 0.50550869, 0.68115331,
       0.14774638, 0.06506393, 0.51205548, 0.31861378, 0.4018829 ,
       0.6981666 , 0.59619521, 0.32928462, 0.72793089, 0.93919746,
       0.66826964, 0.96280109, 0.18169624, 0.17774614, 0.69488767,
       0.45223036, 0.78243653, 0.77974068, 0.22527349, 0.0924354 ,
       0.94813544, 0.14075113, 0.33128638, 0.09346622, 0.0733552 ,
       0.2336682 , 0.91754699, 0.38105824, 0.9381083 , 0.14235331,
       0.08795359, 0.42568003, 0.661871  , 0.73171513, 0.32020269,
       0.31454569, 0.52712535, 0.56875717, 0.8482042 , 0.85251967,
       0.99138374, 0.02860771, 0.80366042, 0.02855285, 0.96809202,
       0.4383295 , 0.56626347, 0.00709193, 0.28396105, 0.93841214,
       0.37939066, 0.48577452, 0.49173335, 0.93875022, 0.08825134,
       0.91544997, 0.57155383, 0.21179782, 0.95138422, 0.60492146,
       0.59608717, 0.01416868, 0.3673404 , 0.0536157 , 0.34105833,
       0.29895624, 0.56594144, 0.44432274, 0.8436724 , 0.5528569 ,
       0.0564989 , 0.98724688, 0.49126278, 0.00582861, 0.65958956,
       0.25203093, 0.8752154 , 0.59609441, 0.65463154, 0.21465251,
       0.66000606, 0.72215396, 0.89475172, 0.21875189, 0.44039621]), 'time': datetime.datetime(2024, 4, 14, 9, 27, 9, 135046)}}

9.2.1. Access to data by attributes#

The access by key names can be tedious, when deeply nested dictionaries are involved. We provide a handling via attributes like this

accessible_by_attribute = file.as_attr()
accessible_by_attribute.data_sets.second
{'data': array([0.79793627, 0.57472844, 0.90132713, 0.44413594, 0.63851354,
        0.87542551, 0.15202641, 0.40396641, 0.80832456, 0.93028275,
        0.54981723, 0.13032565, 0.66825968, 0.50550869, 0.68115331,
        0.14774638, 0.06506393, 0.51205548, 0.31861378, 0.4018829 ,
        0.6981666 , 0.59619521, 0.32928462, 0.72793089, 0.93919746,
        0.66826964, 0.96280109, 0.18169624, 0.17774614, 0.69488767,
        0.45223036, 0.78243653, 0.77974068, 0.22527349, 0.0924354 ,
        0.94813544, 0.14075113, 0.33128638, 0.09346622, 0.0733552 ,
        0.2336682 , 0.91754699, 0.38105824, 0.9381083 , 0.14235331,
        0.08795359, 0.42568003, 0.661871  , 0.73171513, 0.32020269,
        0.31454569, 0.52712535, 0.56875717, 0.8482042 , 0.85251967,
        0.99138374, 0.02860771, 0.80366042, 0.02855285, 0.96809202,
        0.4383295 , 0.56626347, 0.00709193, 0.28396105, 0.93841214,
        0.37939066, 0.48577452, 0.49173335, 0.93875022, 0.08825134,
        0.91544997, 0.57155383, 0.21179782, 0.95138422, 0.60492146,
        0.59608717, 0.01416868, 0.3673404 , 0.0536157 , 0.34105833,
        0.29895624, 0.56594144, 0.44432274, 0.8436724 , 0.5528569 ,
        0.0564989 , 0.98724688, 0.49126278, 0.00582861, 0.65958956,
        0.25203093, 0.8752154 , 0.59609441, 0.65463154, 0.21465251,
        0.66000606, 0.72215396, 0.89475172, 0.21875189, 0.44039621]),
 'time': datetime.datetime(2024, 4, 14, 9, 27, 9, 135046)}

9.3. Writing files to disk#

In order to make your changes persistent, we are going to save the memory-backed file to disk by invoking WeldxFile.write_to.

file.write_to("example.asdf")
'example.asdf'

This newly created file can be opened up again, in read-write mode like by passing the appropriate arguments.

example = WeldxFile("example.asdf", mode="rw")
example["updated"] = True
example.close()

Note, that we closed the file here explicitly. Before closing, we wanted to write a simple item to tree. But lets see what happens, if we open the file once again.

example = WeldxFile("example.asdf", mode="rw")
display(example)
example.close()
#ASDF 1.0.0
#ASDF_STANDARD 1.5.0
%YAML 1.1
%TAG ! tag:stsci.edu:asdf/
--- !core/asdf-1.1.0
asdf_library: !core/software-1.0.0 {author: The ASDF Developers, homepage: 'http://github.com/asdf-format/asdf',
  name: asdf, version: 2.15.1}
history:
  extensions:
  - !core/extension_metadata-1.0.0
    extension_class: asdf.extension.BuiltinExtension
    software: !core/software-1.0.0 {name: asdf, version: 2.15.1}
data_sets:
  second:
    data: !core/ndarray-1.0.0
      data: []
      datatype: float64
      shape: [100]
    time: 2024-04-14 09:27:09.135046
  time: 2024-04-14 09:27:09.048219
updated: true

As you see the updated state has been written, because we closed the file properly. If we omit closing the file, our changes would be lost when the object runs out of scope or Python terminates.

9.4. Handling updates within a context manager#

To ensure you will not forget to update your file after making changes, we are able to enclose our file-changing operations within a context manager. This ensures that all operations done in this context (the with block) are being written to the file, once the context is left. Note that the underlying file is also closed after the context ends. This is useful, when you have to update lots of files, as there is a limited amount of file handles an operating system can deal with.

with WeldxFile("example.asdf", mode="rw") as example:
    example["updated"] = True
    fh = example.file_handle
    # now the context ends, and the file is being saved to disk again.

# lets check the file handle has been closed, after the context ended.
assert fh.closed

Let us inspect the file once again, to see whether our updated item has been correctly written.

WeldxFile("example.asdf")
#ASDF 1.0.0
#ASDF_STANDARD 1.5.0
%YAML 1.1
%TAG ! tag:stsci.edu:asdf/
--- !core/asdf-1.1.0
asdf_library: !core/software-1.0.0 {author: The ASDF Developers, homepage: 'http://github.com/asdf-format/asdf',
  name: asdf, version: 2.15.1}
history:
  extensions:
  - !core/extension_metadata-1.0.0
    extension_class: asdf.extension.BuiltinExtension
    software: !core/software-1.0.0 {name: asdf, version: 2.15.1}
data_sets:
  second:
    data: !core/ndarray-1.0.0
      data: []
      datatype: float64
      shape: [100]
    time: 2024-04-14 09:27:09.135046
  time: 2024-04-14 09:27:09.048219
updated: true

In case an error got triggered (e.g. an exception has been raised) inside the context, the underlying file is still updated. You could prevent this behavior, by passing sync=False during file construction.

try:
    with WeldxFile("example.asdf", mode="rw") as file:
        file["updated"] = False
        raise Exception("oh no")
except Exception as e:
    print("expected error:", e)
expected error: oh no
WeldxFile("example.asdf")
#ASDF 1.0.0
#ASDF_STANDARD 1.5.0
%YAML 1.1
%TAG ! tag:stsci.edu:asdf/
--- !core/asdf-1.1.0
asdf_library: !core/software-1.0.0 {author: The ASDF Developers, homepage: 'http://github.com/asdf-format/asdf',
  name: asdf, version: 2.15.1}
history:
  extensions:
  - !core/extension_metadata-1.0.0
    extension_class: asdf.extension.BuiltinExtension
    software: !core/software-1.0.0 {name: asdf, version: 2.15.1}
data_sets:
  second:
    data: !core/ndarray-1.0.0
      data: []
      datatype: float64
      shape: [100]
    time: 2024-04-14 09:27:09.135046
  time: 2024-04-14 09:27:09.048219
updated: false

9.5. Keeping a log of changes when manipulating a file#

It can become quite handy to know what has been done to file in the past. Weldx files provide a history log, in which arbitrary strings can be stored with time stamps and used software. We quickly run you through the process of adding history entries to your file.

filename_hist = "example_history.asdf"
with WeldxFile(filename_hist, mode="rw") as file:
    file["some"] = "changes"
    file.add_history_entry("added some changes")
WeldxFile(filename_hist).history
[{'description': 'added some changes',
  'software': {'author': 'BAM',
   'homepage': 'https://www.bam.de/Content/EN/Projects/WelDX/weldx.html',
   'name': 'weldx',
   'version': '0.6.8.dev29+g0e0a9ea.d20240414'},
  'time': datetime.datetime(2024, 4, 14, 9, 27, 9)}]

When you want to describe a custom software, which is lets say a library or tool used to generate/modify the data in the file and we passed it into the creation of our WeldxFile.

software = dict(
    name="my_tool", version="1.0", homepage="https://my_tool.org", author="the crowd"
)
with WeldxFile(filename_hist, mode="rw", software_history_entry=software) as file:
    file["some"] = "changes"
    file.add_history_entry("added more changes")

Let’s now inspect how we wrote history.

WeldxFile(filename_hist).history[-1]
{'description': 'added more changes',
 'software': {'author': 'the crowd',
  'homepage': 'https://my_tool.org',
  'name': 'my_tool',
  'version': '1.0'},
 'time': datetime.datetime(2024, 4, 14, 9, 27, 9)}

The entries key is a list of all log entries, where new entries are appended to. We have proper time stamps indicating when the change happened, the actual log entry, and optionally a custom software used to make the change.

9.6. Handling of custom schemas#

An important aspect of WelDX or ASDF files is, that you can validate them to comply with a defined schema. A schema defines required and optional attributes a tree structure has to provide to pass the schema validation. Further the types of these attributes can be defined, e.g. the data attribute should be a NumPy array, or a timestamp should be of type pandas.Timestamp. There are several schemas provided by WelDX, which can be used by passing them to the custom_schema argument. It is expected to be a path-like type, so a string (str) or pathlib.Path is accepted. The provided utility function get_schema_path returns the path to named schema. So its output can directly be used in WeldxFile(schema=…)

from weldx.asdf.util import get_schema_path
schema = get_schema_path("single_pass_weld-0.1.0")
schema
PosixPath('/home/docs/checkouts/readthedocs.org/user_builds/weldx/conda/latest/lib/python3.10/site-packages/weldx/schemas/weldx.bam.de/weldx/datamodels/single_pass_weld-0.1.0.yaml')

This schema defines a complete experimental setup with measurement data, e.g requires the following attributes to be defined in our tree:

  • workpiece

  • TCP

  • welding_current

  • welding_voltage

  • measurements

  • equipment

We use a testing function to provide this data now, and validate it against the schema by passing the custom_schema during WeldxFile creation. Here we just have a look at the process parameters sub-dictionary.

from weldx.asdf.cli.welding_schema import single_pass_weld_example

_, single_pass_weld_data = single_pass_weld_example(out_file=None)
display(single_pass_weld_data["process"])
{'welding_process': GmawProcess(base_process='pulse', manufacturer='CLOOS', power_source='Quinto', parameters={'wire_feedrate': <TimeSeries>
 Constant value:
 	10.0
 Units:
 	m / min
 , 'pulse_voltage': <TimeSeries>
 Constant value:
 	40.0
 Units:
 	V
 , 'pulse_duration': <TimeSeries>
 Constant value:
 	5.0
 Units:
 	ms
 , 'pulse_frequency': <TimeSeries>
 Constant value:
 	100.0
 Units:
 	Hz
 , 'base_current': <TimeSeries>
 Constant value:
 	60.0
 Units:
 	A
 }, tag='CLOOS/pulse', meta={'modulation': 'UI'}),
 'shielding_gas': ShieldingGasForProcedure(use_torch_shielding_gas=True, torch_shielding_gas=ShieldingGasType(gas_component=[GasComponent(gas_chemical_name='argon', gas_percentage=<Quantity(82, 'percent')>), GasComponent(gas_chemical_name='carbon dioxide', gas_percentage=<Quantity(18, 'percent')>)], common_name='SG', designation=None), torch_shielding_gas_flowrate=<Quantity(20, 'liter / minute')>, use_backing_gas=None, backing_gas=None, backing_gas_flowrate=None, use_trailing_gas=None, trailing_shielding_gas=None, trailing_shielding_gas_flowrate=None),
 'weld_speed': <TimeSeries>
 Constant value:
 	10
 Units:
 	mm / s,
 'welding_wire': {'diameter': array(1.2) <Unit('millimeter')>}}

That is a lot of data, containing complex data structures and objects describing the whole experiment including measurement data. We can now create new WeldxFile and validate the data against the schema.

WeldxFile(tree=single_pass_weld_data, custom_schema=schema, mode="rw")
#ASDF 1.0.0
#ASDF_STANDARD 1.5.0
%YAML 1.1
%TAG ! tag:stsci.edu:asdf/
%TAG !weldx! asdf://weldx.bam.de/weldx/tags/
--- !core/asdf-1.1.0
asdf_library: !core/software-1.0.0 {author: The ASDF Developers, homepage: 'http://github.com/asdf-format/asdf',
  name: asdf, version: 2.15.1}
history:
  extensions:
  - !core/extension_metadata-1.0.0
    extension_class: asdf.extension.BuiltinExtension
    software: !core/software-1.0.0 {name: asdf, version: 2.15.1}
  - !core/extension_metadata-1.0.0
    extension_class: weldx.asdf.extension.WeldxExtension
    extension_uri: asdf://weldx.bam.de/weldx/extensions/weldx-0.1.2
    software: !core/software-1.0.0 {name: weldx, version: 0.6.8.dev29+g0e0a9ea.d20240414}
TCP: !weldx!core/transformations/local_coordinate_system-0.1.1
  time: !weldx!time/timedeltaindex-0.1.0
    values: !core/ndarray-1.0.0
      data: []
      datatype: int64
      shape: [2]
    start: !weldx!time/timedelta-0.1.0 P0DT0H0M0S
    end: !weldx!time/timedelta-0.1.0 P0DT0H0M29S
    min: !weldx!time/timedelta-0.1.0 P0DT0H0M0S
    max: !weldx!time/timedelta-0.1.0 P0DT0H0M29S
  orientations: !weldx!core/variable-0.1.1
    name: orientations
    dimensions: [c, v]
    dtype: <f8
    data: !core/ndarray-1.0.0
      data: []
      datatype: float64
      shape: [3, 3]
  coordinates: !weldx!core/variable-0.1.1
    name: coordinates
    dimensions: [time, c]
    dtype: <f8
    units: !weldx!units/units-0.1.0 millimeter
    data: !core/ndarray-1.0.0
      data: []
      datatype: float64
      shape: [2, 3]
coordinate_systems: !weldx!core/transformations/coordinate_system_hierarchy-0.1.0
  name: Coordinate system manager 0
  graph: !weldx!core/graph/di_graph-0.1.0
    root_node: !weldx!core/graph/di_node-0.1.0
      name: base
      attributes:
        data: {}
      edges:
      - !weldx!core/graph/di_edge-0.1.0
        direction: bwd
        attributes:
          defined: true
          transformation: !weldx!core/transformations/local_coordinate_system-0.1.1 {}
        target_node: !weldx!core/graph/di_node-0.1.0
          name: workpiece
          attributes:
            data: {}
          edges:
          - !weldx!core/graph/di_edge-0.1.0
            direction: bwd
            attributes:
              defined: true
              transformation: !weldx!core/transformations/local_coordinate_system-0.1.1
                time: !weldx!time/timedeltaindex-0.1.0
                  values: !core/ndarray-1.0.0
                    data: []
                    datatype: int64
                    shape: [2]
                  start: !weldx!time/timedelta-0.1.0 P0DT0H0M0S
                  end: !weldx!time/timedelta-0.1.0 P0DT0H0M29S
                  min: !weldx!time/timedelta-0.1.0 P0DT0H0M0S
                  max: !weldx!time/timedelta-0.1.0 P0DT0H0M29S
                orientations: !weldx!core/variable-0.1.1
                  name: orientations
                  dimensions: [c, v]
                  dtype: <f8
                  data: !core/ndarray-1.0.0
                    data: []
                    datatype: float64
                    shape: [3, 3]
                coordinates: !weldx!core/variable-0.1.1
                  name: coordinates
                  dimensions: [time, c]
                  dtype: <f8
                  units: !weldx!units/units-0.1.0 millimeter
                  data: !core/ndarray-1.0.0
                    data: []
                    datatype: float64
                    shape: [2, 3]
            target_node: !weldx!core/graph/di_node-0.1.0
              name: tcp_wire
              attributes:
                data: {}
              edges:
              - !weldx!core/graph/di_edge-0.1.0
                direction: bwd
                attributes:
                  defined: true
                  transformation: !weldx!core/transformations/local_coordinate_system-0.1.1
                    coordinates: !weldx!core/variable-0.1.1
                      name: coordinates
                      dimensions: [c]
                      dtype: <f8
                      units: !weldx!units/units-0.1.0 millimeter
                      data: !core/ndarray-1.0.0
                        data: []
                        datatype: float64
                        shape: [3]
                target_node: !weldx!core/graph/di_node-0.1.0
                  name: tcp_contact
                  attributes:
                    data: {}
  subsystems: []
equipment:
- &id008 !weldx!equipment/measurement_equipment-0.1.0
  name: HKS P1000-S3
  sources:
  - &id003 !weldx!measurement/source-0.1.0
    name: Current Sensor
    output_signal: &id004 !weldx!measurement/signal-0.1.0
      signal_type: analog
      units: !weldx!units/units-0.1.0 volt
    error: !weldx!measurement/error-0.1.0
      deviation: !weldx!units/quantity-0.1.0 {value: 0.1, units: !weldx!units/units-0.1.0 percent}
  - &id011 !weldx!measurement/source-0.1.0
    name: Voltage Sensor
    output_signal: &id012 !weldx!measurement/signal-0.1.0
      signal_type: analog
      units: !weldx!units/units-0.1.0 volt
    error: !weldx!measurement/error-0.1.0
      deviation: !weldx!units/quantity-0.1.0 {value: 0.1, units: !weldx!units/units-0.1.0 percent}
  transformations: []
- &id005 !weldx!equipment/measurement_equipment-0.1.0
  name: Beckhoff ELM3002-0000
  sources: []
  transformations:
  - &id006 !weldx!measurement/signal_transformation-0.1.0
    name: AD conversion current measurement
    error: !weldx!measurement/error-0.1.0
      deviation: !weldx!units/quantity-0.1.0 {value: 0.01, units: !weldx!units/units-0.1.0 percent}
    func: !weldx!core/mathematical_expression-0.1.0
      expression: a*x + b
      parameters:
        a: !weldx!units/quantity-0.1.0 {value: 3276.8, units: !weldx!units/units-0.1.0 1
            / volt}
        b: !weldx!units/quantity-0.1.0 {value: 0.0, units: !weldx!units/units-0.1.0 dimensionless}
    type_transformation: AD
  - &id013 !weldx!measurement/signal_transformation-0.1.0
    name: AD conversion voltage measurement
    error: !weldx!measurement/error-0.1.0
      deviation: !weldx!units/quantity-0.1.0 {value: 0.01, units: !weldx!units/units-0.1.0 percent}
    func: !weldx!core/mathematical_expression-0.1.0
      expression: a*x + b
      parameters:
        a: !weldx!units/quantity-0.1.0 {value: 3276.8, units: !weldx!units/units-0.1.0 1
            / volt}
        b: !weldx!units/quantity-0.1.0 {value: 0.0, units: !weldx!units/units-0.1.0 dimensionless}
    type_transformation: AD
measurements:
- !weldx!measurement/measurement-0.1.0
  name: welding current measurement
  data:
  - &id007 !weldx!core/time_series-0.1.1
    &id001 values: &id002 !core/ndarray-1.0.0
      data: []
      datatype: float64
      shape: [6]
    time: !weldx!time/time-0.1.0
      values: !weldx!time/timedeltaindex-0.1.0 {start: !weldx!time/timedelta-0.1.0 P0DT0H0M0S,
        end: !weldx!time/timedelta-0.1.0 P0DT0H0M10S, freq: 2s, min: !weldx!time/timedelta-0.1.0 P0DT0H0M0S,
        max: !weldx!time/timedelta-0.1.0 P0DT0H0M10S}
    units: !weldx!units/units-0.1.0 ampere
    shape: [6]
    interpolation: step
    *id001 : *id002
  measurement_chain: !weldx!measurement/measurement_chain-0.1.0
    name: welding current measurement chain
    data_source: *id003
    graph: !weldx!core/graph/di_graph-0.1.0
      root_node: !weldx!core/graph/di_node-0.1.0
        name: Current Sensor
        attributes:
          signal: *id004
        edges:
        - !weldx!core/graph/di_edge-0.1.0
          direction: fwd
          attributes:
            equipment: *id005
            transformation: *id006
          target_node: !weldx!core/graph/di_node-0.1.0
            name: AD conversion current measurement
            attributes:
              signal: !weldx!measurement/signal-0.1.0
                signal_type: digital
                units: !weldx!units/units-0.1.0 dimensionless
            edges:
            - !weldx!core/graph/di_edge-0.1.0
              direction: fwd
              attributes:
                transformation: !weldx!measurement/signal_transformation-0.1.0
                  name: Calibration current measurement
                  error: !weldx!measurement/error-0.1.0
                    deviation: 0.0
                  func: !weldx!core/mathematical_expression-0.1.0
                    expression: a*x + b
                    parameters:
                      a: !weldx!units/quantity-0.1.0 {value: 0.030517578125, units: !weldx!units/units-0.1.0 ampere}
                      b: !weldx!units/quantity-0.1.0 {value: 0.0, units: !weldx!units/units-0.1.0 ampere}
                  wx_metadata:
                    software: !core/software-1.0.0 {name: Beckhoff TwinCAT ScopeView,
                      version: 3.4.3143}
              target_node: !weldx!core/graph/di_node-0.1.0
                name: Calibration current measurement
                attributes:
                  signal: !weldx!measurement/signal-0.1.0
                    signal_type: digital
                    units: !weldx!units/units-0.1.0 ampere
                    data: *id007
    source_equipment: *id008
- !weldx!measurement/measurement-0.1.0
  name: welding voltage measurement
  data:
  - &id014 !weldx!core/time_series-0.1.1
    &id009 values: &id010 !core/ndarray-1.0.0
      data: []
      datatype: float64
      shape: [6]
    time: !weldx!time/time-0.1.0
      values: !weldx!time/timedeltaindex-0.1.0 {start: !weldx!time/timedelta-0.1.0 P0DT0H0M0S,
        end: !weldx!time/timedelta-0.1.0 P0DT0H0M10S, freq: 2s, min: !weldx!time/timedelta-0.1.0 P0DT0H0M0S,
        max: !weldx!time/timedelta-0.1.0 P0DT0H0M10S}
    units: !weldx!units/units-0.1.0 volt
    shape: [6]
    interpolation: step
    *id009 : *id010
  measurement_chain: !weldx!measurement/measurement_chain-0.1.0
    name: welding voltage measurement chain
    data_source: *id011
    graph: !weldx!core/graph/di_graph-0.1.0
      root_node: !weldx!core/graph/di_node-0.1.0
        name: Voltage Sensor
        attributes:
          signal: *id012
        edges:
        - !weldx!core/graph/di_edge-0.1.0
          direction: fwd
          attributes:
            equipment: *id005
            transformation: *id013
          target_node: !weldx!core/graph/di_node-0.1.0
            name: AD conversion voltage measurement
            attributes:
              signal: !weldx!measurement/signal-0.1.0
                signal_type: digital
                units: !weldx!units/units-0.1.0 dimensionless
            edges:
            - !weldx!core/graph/di_edge-0.1.0
              direction: fwd
              attributes:
                transformation: !weldx!measurement/signal_transformation-0.1.0
                  name: Calibration voltage measurement
                  error: !weldx!measurement/error-0.1.0
                    deviation: 0.0
                  func: !weldx!core/mathematical_expression-0.1.0
                    expression: a*x + b
                    parameters:
                      a: !weldx!units/quantity-0.1.0 {value: 0.0030517578125, units: !weldx!units/units-0.1.0 volt}
                      b: !weldx!units/quantity-0.1.0 {value: 0.0, units: !weldx!units/units-0.1.0 volt}
                  wx_metadata:
                    software: !core/software-1.0.0 {name: Beckhoff TwinCAT ScopeView,
                      version: 3.4.3143}
              target_node: !weldx!core/graph/di_node-0.1.0
                name: Calibration voltage measurement
                attributes:
                  signal: !weldx!measurement/signal-0.1.0
                    signal_type: digital
                    units: !weldx!units/units-0.1.0 volt
                    data: *id014
    source_equipment: *id008
process:
  shielding_gas: !weldx!aws/process/shielding_gas_for_procedure-0.1.0
    use_torch_shielding_gas: true
    torch_shielding_gas: !weldx!aws/process/shielding_gas_type-0.1.0
      gas_component:
      - !weldx!aws/process/gas_component-0.1.0
        gas_chemical_name: argon
        gas_percentage: !weldx!units/quantity-0.1.0 {value: 82, units: !weldx!units/units-0.1.0 percent}
      - !weldx!aws/process/gas_component-0.1.0
        gas_chemical_name: carbon dioxide
        gas_percentage: !weldx!units/quantity-0.1.0 {value: 18, units: !weldx!units/units-0.1.0 percent}
      common_name: SG
    torch_shielding_gas_flowrate: !weldx!units/quantity-0.1.0 {value: 20, units: !weldx!units/units-0.1.0 liter
        / minute}
  weld_speed: !weldx!core/time_series-0.1.1
    units: !weldx!units/units-0.1.0 millimeter / second
    value: 10
  welding_process: !weldx!process/CLOOS/pulse-0.1.0
    base_process: pulse
    manufacturer: CLOOS
    meta: {modulation: UI}
    parameters:
      base_current: !weldx!core/time_series-0.1.1
        units: !weldx!units/units-0.1.0 ampere
        value: 60.0
      pulse_duration: !weldx!core/time_series-0.1.1
        units: !weldx!units/units-0.1.0 millisecond
        value: 5.0
      pulse_frequency: !weldx!core/time_series-0.1.1
        units: !weldx!units/units-0.1.0 hertz
        value: 100.0
      pulse_voltage: !weldx!core/time_series-0.1.1
        units: !weldx!units/units-0.1.0 volt
        value: 40.0
      wire_feedrate: !weldx!core/time_series-0.1.1
        units: !weldx!units/units-0.1.0 meter / minute
        value: 10.0
    power_source: Quinto
    tag: CLOOS/pulse
  welding_wire:
    diameter: !weldx!units/quantity-0.1.0 {value: 1.2, units: !weldx!units/units-0.1.0 millimeter}
reference_timestamp: !weldx!time/timestamp-0.1.0 2020-11-09T12:00:00
welding_current: *id007
welding_voltage: *id014
workpiece:
  base_metal: {common_name: S355J2+N, standard: 'DIN EN 10225-2:2011'}
  geometry:
    groove_shape: !weldx!groove/iso_9692_1_2013_12/VGroove-0.1.0
      t: !weldx!units/quantity-0.1.0 {value: 5, units: !weldx!units/units-0.1.0 millimeter}
      alpha: !weldx!units/quantity-0.1.0 {value: 50, units: !weldx!units/units-0.1.0 degree}
      b: !weldx!units/quantity-0.1.0 {value: 1, units: !weldx!units/units-0.1.0 millimeter}
      c: !weldx!units/quantity-0.1.0 {value: 1, units: !weldx!units/units-0.1.0 millimeter}
      code_number: ['1.3', '1.5']
    seam_length: !weldx!units/quantity-0.1.0 {value: 300, units: !weldx!units/units-0.1.0 millimeter}
wx_metadata: {welder: A.W. Elder}

But what would happen, if we forget an import attribute? Lets have a closer look…

# simulate we forgot something important, so we delete the workpiece:
del single_pass_weld_data["workpiece"]

# now create the file again, and see what happens:
try:
    WeldxFile(tree=single_pass_weld_data, custom_schema=schema, mode="rw")
except Exception as e:
    display(e)
<ValidationError: "'workpiece' is a required property">

We receive a ValidationError from the ASDF library, which tells us exactly what the missing information is. The same will happen, if we accidentally pass the wrong type.

# simulate a wrong type by changing it to a NumPy array.
single_pass_weld_data["welding_current"] = np.zeros(10)

# now create the file again, and see what happens:
try:
    WeldxFile(tree=single_pass_weld_data, custom_schema=schema, mode="rw")
except Exception as e:
    display(e)
<ValidationError: "mismatched tags, wanted 'asdf://weldx.bam.de/weldx/tags/core/time_series-0.1.*', got 'tag:stsci.edu:asdf/core/ndarray-1.0.0'">

Here we see, that a signal tag is expected, but a asdf/core/ndarray-1.0.0 was received. The ASDF library assigns tags to certain types to handle their storage in the file format. As shown, the signal tag is contained in weldx/measurement container, provided by weldx.bam.de. The tags and schemas also provide a version number, so future updates in the software become manageable.

Custom schemas can be used to define own protocols or standards describing your data.

9.7. Summary#

In this tutorial we have encountered how to easily open, inspect, manipulate, and update ASDF files created by WelDX. We’ve learned that these files can store a variety of different data types and structures.

Discussed features:

  • Opening in read/write mode WeldxFile(mode="rw").

  • Creating files in memory (passing no file name to WeldxFile() constructor).

  • Writing to disk (WeldxFile.write_to).

  • Keeping log of changes (WeldxFile.history, WeldxFile.add_history_entry).

  • Validation against a schema WeldxFile(custom_schema="/path/my_schema.yaml")