Examples

Measurements Example

The upstream measurements.py example explained — string and numeric measurements, inline kwargs declaration, validators, units, a multi-dimensional power time series converted to a pandas DataFrame, and marginal limits.

Last updated · Verified with OpenHTF 1.6.1

Every way to declare and set a measurement, in one script. Based on examples/measurements.py. Needs pip install pandas (or openhtf[examples]) for the DataFrame step.

measurements.py
import os.path
import random

import openhtf as htf
from openhtf.output.callbacks import json_factory
from openhtf.util import validators


# Simplest form: a Measurement object with no validator.
@htf.measures(htf.Measurement('hello_world_measurement'))
def hello_phase(test):
    test.measurements.hello_world_measurement = 'Hello!'


# Shorthand: a bare string creates the Measurement for you.
@htf.measures('hello_again_measurement')
def again_phase(test):
    test.measurements.hello_again_measurement = 'Again!'


# Several per decorator, and decorators stack.
@htf.measures('first_measurement', 'second_measurement')
@htf.measures(htf.Measurement('third'), htf.Measurement('fourth'))
def lots_of_measurements(test):
    test.measurements.first_measurement = 'First!'
    # Index access works too — handy in loops.
    test.measurements['second_measurement'] = 'Second :('
    for measurement in ('third', 'fourth'):
        test.measurements[measurement] = measurement + ' is the best!'


# Validator, docstring and unit.
@htf.measures(
    htf.Measurement('validated_measurement')
    .in_range(0, 10)
    .doc('This measurement is validated.')
    .with_units(htf.units.SECOND)
)
def measure_seconds(test):
    test.measurements.validated_measurement = 5      # PASS: 0 <= 5 <= 10


# The same attributes as keyword arguments (exactly one measurement per decorator).
@htf.measures(
    'inline_kwargs',
    docstring='This measurement is declared inline!',
    units=htf.units.HERTZ,
    validators=[validators.in_range(0, 10)],
)
@htf.measures('another_inline', docstring='Because why not?')
def inline_phase(test):
    test.measurements.inline_kwargs = 15              # FAIL: outside 0..10
    test.measurements.another_inline = 'This one is unvalidated.'
    test.logger.info('Set inline_kwargs to a failing value, test should FAIL!')


# Multi-dimensional: three input axes, value ignored (0), then analysed as a DataFrame.
@htf.measures(
    htf.Measurement('power_time_series').with_dimensions('ms', 'V', 'A')
)
@htf.measures(htf.Measurement('average_voltage').with_units('V'))
@htf.measures(htf.Measurement('average_current').with_units('A'))
@htf.measures(htf.Measurement('resistance').with_units('ohm').in_range(9, 11))
def multdim_measurements(test):
    for t in range(10):
        resistance = 10
        voltage = 10 + 10.0 * t
        current = voltage / resistance + 0.01 * random.random()
        test.measurements['power_time_series'][(t, voltage, current)] = 0

    dim_measured_value = test.measurements['power_time_series']
    power_df = dim_measured_value.to_dataframe(columns=['ms', 'V', 'A', 'n/a'])
    test.logger.info('This is what a dataframe looks like:\n%s', power_df)
    test.measurements['average_voltage'] = power_df['V'].mean()
    test.measurements['average_current'] = power_df.to_numpy().mean(axis=0)[2]
    test.measurements['resistance'] = (
        test.measurements['average_voltage'] / test.measurements['average_current']
    )


# Marginal limits: passes, but flagged when between the marginal and hard limits.
@htf.measures(
    htf.Measurement('resistance')
    .with_units('ohm')
    .in_range(minimum=5, maximum=17, marginal_minimum=9, marginal_maximum=11)
)
def marginal_measurements(test):
    test.measurements.resistance = 13                 # PASS (MARGINAL)


def create_and_run_test(output_dir: str = '.'):
    test = htf.Test(
        hello_phase,
        again_phase,
        lots_of_measurements,
        measure_seconds,
        inline_phase,
        multdim_measurements,
        marginal_measurements,
    )
    test.add_output_callbacks(
        json_factory.OutputToJSON(os.path.join(output_dir, 'measurements.json'), indent=2)
    )
    test.execute(test_start=lambda: 'MyDutId')


if __name__ == '__main__':
    create_and_run_test()
Terminal
$ python measurements.py

================== test: openhtf_test  outcome: FAIL (MARGINAL) =================

The run fails because inline_kwargs is 15 against a 0–10 limit, and is flagged marginal because resistance = 13 sits between the marginal maximum (11) and the hard maximum (17).

What it shows

Three declaration styles

htf.Measurement('name') objects, bare strings, and name + kwargs. Mixing objects and strings in one decorator works but upstream recommends one style per decorator. Measurements →

Attribute or index access

test.measurements.x = ... and test.measurements['x'] = ... are equivalent; the index form allows loops and computed names.

Validators from openhtf.util.validators

validators.in_range(0, 10) is the object behind .in_range(0, 10). Use it directly for the kwargs form or for dimension_pivot_validate. Validators reference →

with_dimensions('ms', 'V', 'A')

Dimensions accept unit strings, UnitDescriptors or htf.Dimension(...). The stored value here is a dummy 0; the axes carry the data — a common trick for a table of samples.

to_dataframe(columns=[...])

DimensionedMeasuredValue.to_dataframe() builds a pandas DataFrame with one column per dimension plus the value. Later phases can compute aggregates from it. Multi-dimensional →

marginal_minimum / marginal_maximum

Inner limits that mark a passing value as marginal at measurement, phase and test level — visible in the record's marginal flags and in the console banner. Marginal →

Constraints worth knowing

  • Measurement names must be valid Python identifiers (after with_args substitution) because they are attributes of test.measurements.
  • A name may be declared once per phase. Declaring it on several phases is allowed but makes flattened exports ambiguous.
  • Setting a measurement twice is discouraged; the framework may enforce single assignment in a future release.

Next

On this page

First-pass yield
0%4.1
Track with TofuPilot