<jupyter_start><jupyter_text>`MeasurementChain` Tutorial OverviewThe goal of the `MeasurementChain` class is to describe as detailed as possible how experimental data is acquired.Acquiring this data is a process that usually involves multiple substeps. For example, during a temperature measurement we might have a sensor that produces a voltage correlating with the current temperature.Since we wanted to know the temperature and not some arbitrary voltage, we need to transform the voltage data into temperature data by utilizing the calibration of our sensor.The `MeasurementChain` is build upon 2 basic constructs: Signals and transformations. We always start our measurement chain with an initial signal, generated by a source sensor.In the example we gave before this was a voltage.To get to the data of interest we might need to perform one or more transformation steps were each one yields a new signal.Possible transformations are for example an AD conversion, signal amplification, filtering, or applying a calibration.The last signal of the measurement chain is usually the one we generate our measurement data from by recording its value at certain points in time.We will now discuss the different methods to construct a `MeasurementChain` and the features it offers. Construction without additional classesThe easiest way to construct a measurement chain is to use the `from_parameters` and the `create_transformation` methods. With these functions, we do not need to bother with as many extra classes as with the other approaches.We start with the `from_parameters` function to create a new measurement chain. We need to provide 5 parameters to it:- The name of the measurement chain- The name of the source that creates the first, unprocessed measurement signal- The error of the source- The type of the source signal (analog or digital)- The unit of the source signalOptionally, one can also provide the associated measurement data, if it was recorded, but we will discuss this in a later section. Let's start by creation our first measurement chain:<jupyter_code>mc_1 = MeasurementChain.from_parameters(
    name="Temperature measurement chain 1",
    source_name="Thermocouple 1",
    source_error=Error(deviation=Q_(0.1, "percent")),
    output_signal_type="analog",
    output_signal_unit="V",
)<jupyter_output><empty_output><jupyter_text>As you can see, we have created a `MeasurementChain` with the name "Temperature measurement chain 1". It's source is named "Thermocouple 1" and it produces an analog output signal in Volts. The specified measurement error is a fixed value of 0.1%. Next we want to add the first transformation step, the analog-digital conversion of the signal. Therefore, we use the `create_transformation` method of our newly created measurement chain. As the `from_parameters` function, it accepts the name of the transformation, its error, the output signal, and the output unit as parameters. Additionally, we can provide a function that describes how the numerical values and units are transformed.The output signal type, unit and function are all optional parameters. However, providing none of them wouldn't apply any changes to the signal and emit a warning.Now we add the AD conversion to our measurement chain:<jupyter_code>mc_1.create_transformation(
    name="AD conversion",
    error=None,
    output_signal_type="digital",
    output_signal_unit="",
)<jupyter_output><empty_output><jupyter_text>All we needed to specify apart from the name and error was the output signal type as "digital". We also removed the unit by providing an empty string as output unit since the AD conversion just yields a digital number that doesn't necessarily represent a physical quantity. Let's add the final transformation, the calibration, which produces the data we are interested in.<jupyter_code>mc_1.create_transformation(
    name="Calibration",
    error=Error(Q_(0.4, "percent")),
    func=MathematicalExpression(
        expression="a*x+b", parameters={"a": Q_(3, "K"), "b": Q_(273.15, "K")}
    ),
)<jupyter_output><empty_output><jupyter_text>Here we specify a function that describes the transformation of our digital number into an actual temperature value:$$3K \cdot x + 273.15K$$The name of the variable and the parameters can be arbitrarily chosen. The only restriction is, that the function only has a single variable which represents the input signal. In this example our input variable is given by `x`.Since the parameters of our function already contain the unit conversion we do not need to provide the `output_signal_unit` parameter. However, we could do this to assure that the functions' output signal has the correct dimensionality, for example length, time, or temperature.To do so, we can provide an arbitrary unit of the desired dimension.If we would pass `m`, we expect the output signal to represent a length.In case the function yields inches, millimeters, yards etc. this would be the correct dimensionality.Squaremeters, seconds, or volts would raise an exception.Additionally, you can't use the `output_signal_unit` parameter to add a unit conversion if the passed function does not contain one. In fact, if only `output_signal_unit` is provided without a function, like in the AD conversion we added before, `create_transformation` generates a corresponding conversion function internally. Plotting measurement chainsNow that we have created our first measurement chain without any exceptions, we might want to verify that everything is specified correctly. To do so, we can use pythons `print` command to check all variables, but this would be a bit tedious. A more convenient way is to use the plot function of the `MeasurementChain`.<jupyter_code>mc_1.plot()<jupyter_output><empty_output><jupyter_text>The plot shows us the initial signal produced by the source on the left. To the right of the source signal all transformations and their resulting output signals are shown. Construction from dedicated classesThe first method we demonstrated required you to provide a lot of parameters. While this method is very explicit and doesn't involve many other classes, it is not the best approach if you want to share sources and transformations with other measurement chains or objects. To share information about a source or transformation, two dedicated container classes are available: `SignalSource` and `SignalTransformation`.A `SignalSource` can be generated as follows:<jupyter_code>source_2 = SignalSource(
    name="Source",
    error=Error(Q_(0.1, "percent")),
    output_signal=Signal(signal_type="analog", units="V"),
)<jupyter_output><empty_output><jupyter_text>The information we provide is similar as before. The only noteworthy thing here is, that the signal type and unit are wrapped into a separate `Signal` class. Now we can use this class to create a measurement chain:<jupyter_code>mc_2 = MeasurementChain(name="Measurement chain 2", source=source_2)
mc_2.plot()<jupyter_output><empty_output><jupyter_text>Next we create a `SignalTransformation`<jupyter_code>transformation_3 = SignalTransformation(
    name="Transformation",
    error=Error(Q_(1, "percent")),
    func=MathematicalExpression(
        expression="a*x+b", parameters={"a": Q_(3, "K/V"), "b": Q_(273.15, "K")}
    ),
    type_transformation="AD",
)<jupyter_output><empty_output><jupyter_text>The first three arguments are equivalent as when using the `create_transformation` method. The `type_transformation` parameter expects a string consisting of two letters that can either be "A" for analog and "D" for digital. The first letter is the expected input signal type and the second letter the output signal type.We can add it to the `MeasurementChain` with the `add_transformation` function.<jupyter_code>mc_2.add_transformation(transformation_3)
mc_2.plot()<jupyter_output><empty_output><jupyter_text>Construction from equipment classesThe sources and transformations of a measurement chain are often tied to a certain piece of laboratory equipment. The weldx package offers the `MeasurementEquipment` structure to describe your equipment and collect all the operations it performs inside of a measurement chain.Since lab equipment usually doesn't change frequently, a good approach would be to define all your instruments once and reuse their definitions when creating a new WelDX file.The `MeasurementChain` supports this by letting you create a new instances using `from_equipment` and adding transformations with `add_transformation_from_equipment`.Let us create two pieces of equipment and create a new `MeasurementChain` from it:<jupyter_code>source_eq = MeasurementEquipment("Source 2000", sources=[source_2])
transformation_eq = MeasurementEquipment(
    "Transformer X3", transformations=[transformation_3]
)<jupyter_output><empty_output><jupyter_text>Now we simply create a measurement chain from them:<jupyter_code>mc_3 = MeasurementChain.from_equipment("Measurement Chain 3", source_eq)
mc_3.plot()
mc_3.add_transformation_from_equipment(transformation_eq)
mc_3.plot()<jupyter_output><empty_output><jupyter_text>If we add an equipment to the `MeasurementChain`, it won't just only store the corresponding transformation but also remember the equipment that provides it.We can get the linked equipment using `get_equipment`.Therefore we must provide the name of the transformation or source<jupyter_code>mc_3.get_equipment("Source")<jupyter_output><empty_output><jupyter_text>It might also be the case that an equipment provides multiple sources or transformations.In this case, you need to specify which one should be added to the `MeasurementChain`.The `from_equipment` and `add_transformation_from_equipment` provide an extra parameter for this case. Accessing informationIn case you want to know names of the source or the transformations that are part of a `MeasurementChain`, you can use the following two properties:<jupyter_code>mc_3.source_name
mc_3.transformation_names<jupyter_output><empty_output><jupyter_text>You can also get the the `SignalSource` and `SignalTransformation` objects using:<jupyter_code>mc_3.source
mc_3.get_transformation("Transformation")<jupyter_output><empty_output><jupyter_text>Because a measurement chain can contain multiple transformations, you have to specify the name of the desired transformation when using `get_transformation`.If you want to know the type and unit of a signal that is generated by the source or results from a transformation, you can call the `get_signal` method.It returns a special class that contains all relevant information about a signal, including attached measurement data, that will be covered in the next section.<jupyter_code>mc_3.get_signal("Transformation")<jupyter_output><empty_output><jupyter_text>One can also get a list of all signals and transformations with:<jupyter_code>mc_3.signals
mc_3.transformations<jupyter_output><empty_output><jupyter_text>Attaching dataUntil now we have seen how to create such a `MeasurementChain` and how we can access the information it provides.However, the most important information is currently still missing: the actual data we produced.Attaching data is simply done with `add_signal_data`:<jupyter_code>mc_1.add_signal_data(
    TimeSeries(data=Q_([10, 15, 5], "K"), time=TimedeltaIndex(data=[0, 2, 6], unit="s"))
)<jupyter_output><empty_output><jupyter_text>As you can see in the following plot, the data is associated with the output signal of the last transformation:<jupyter_code>mc_1.plot()<jupyter_output><empty_output><jupyter_text>But what if we want to attach the raw data of our source too? For this case, `add_signal_data` provides a second parameter to specify the origin of the data.To add some data to the source, we do the following:<jupyter_code>mc_1.add_signal_data(
    data=TimeSeries(
        data=Q_([2, 3, 1], "K"), time=TimedeltaIndex(data=[0, 2, 6], unit="s")
    ),
    signal_source="Thermocouple 1",
)<jupyter_output><empty_output><jupyter_text>As can be seen in the next plot, we successfully added the data to the source:<jupyter_code>mc_1.plot()<jupyter_output><empty_output><jupyter_text>Also note, that all functions that let you create a measurement chain or add a transformation provide an extra parameter to add the corresponding data.Finally, if you want to access the stored data, we can use `get_signal_data`.As for the `add_signal_data` function, you get the data from the last transformation if you don't specify any source or transformation name:<jupyter_code>mc_1.get_signal_data()<jupyter_output><empty_output><jupyter_text>We access the sources' data by adding its name to the function call:<jupyter_code>mc_1.get_signal_data("Thermocouple 1")<jupyter_output><empty_output><jupyter_text>Remember, that the returned `TimeSeries` posseses a plot function that lets you create a plot of the time dependent data:<jupyter_code>mc_1.get_signal_data().plot()<jupyter_output><empty_output><jupyter_text>Note that the `Signal` class returned by the `get_signal` function offers a `plot` function too.So you do not need to fetch the data from the Signal in order to plot it:<jupyter_code>mc_1.get_signal("Calibration").plot()<jupyter_output><empty_output>
StarcoderdataJupyterStructuredCleanDedup