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.
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()$ 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 styleshtf.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 accesstest.measurements.x = ... and test.measurements['x'] = ... are equivalent; the index form allows loops and computed names.
Validators from openhtf.util.validatorsvalidators.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_maximumInner 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_argssubstitution) because they are attributes oftest.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
Hello world
The canonical OpenHTF hello_world.py example explained — one phase, one measurement, a JSON output callback and a DUT ID prompt — with its console and JSON output.
With plugs
The upstream with_plugs.py example — write one phase against a plug placeholder, then generate several phases with phase.with_plugs() and with_args(), each with its own name and measurement.