Creating Measurement Classes¶
The lsst.validate.drp.base.MeasurmentBase
abstract base class defines a standard interface for writing classes that make measurements of metrics.
The MeasurementBase
base class ensures that measurements, along with metadata about the measurement, can be serialized and submitted to the QA database and SQUASH dashboard.
This page covers the usage of MeasurementBase
for creating measurement classes.
A Minimal Measurement Class¶
At a minimum, measurement classes must subclass MeasurementBase
and provide some metadata as instance attributes, such as the name of metric being measured and units of the measurement.
This example is a basic template for a measurement:
class PA1Measurement(MeasurementBase):
metric = None
value = None
units = 'mmag'
label = 'PA1'
def __init__(self):
MeasurementBase.__init__(self)
self.metric = Metric.fromYaml(self.label)
# measurement code
# ...
self.value = 0 # Scalar value from measurement code
In a measurement class, the following metadata attributes must be specified (their presence is required by the MeasurementBase
abstract base class):
label
- The name of the metric.
metric
- A
Metric
object. In this example, thefromYaml()
class method constructs aMetric
instance for PA1 from themetrics.yaml
file built intovalidate_drp
. units
- Units of the metric measurement. As in the
Datum
class,units
should be anastropy.units
-compatible string.
The purpose of a measurement class is to make a make a measurement; those calculations should occur in a measurement instance’s __init__
method.
Any data required for a measurement should be provided through the measurement class’s __init__
method.
The measurement result is stored in a value
attribute:
value
- The value attribute should be a scalar (float or int), in the same physical units indicated by the
units
attribute. If a Measurement class is unable to make a measurement,value
should beNone
.
Storing Measurement Parameters¶
Often a measurement code is customized with parameters.
As a means of lightweight provenance, the measurement API provides a way to declare these parameters so that they’re persisted to the database using the registerParameter()
method:
class PA1Measurement(MeasurementBase):
metric = None
value = None
units = 'mmag'
label = 'PA1'
schema = 'pa1-1.0.0'
def __init__(self, numRandomShuffles=50):
MeasurementBase.__init__(self)
self.metric = Metric.fromYaml(self.label)
self.registerParameter('numRandomShuffles',
value=numRandomShuffles,
units='',
description='Number of random shuffles')
# ... measurement code
In this example, the PA1Measurement
class registers a parameter named numRandomShuffles
.
Accessing parameter values as object attributes¶
In addition to registering a parameter for serialization, the registerParameter()
method makes the values of parameters available as instance attributes.
Continuing the PA1Measurement
example:
pa1 = PA1Measurment(numRandomShuffles=50)
pa1.numRandomShuffles # == 50
Through attribute access, a parameter’s value can be both read and updated.
Accessing parameters as Datum
objects¶
Although the values of parameters can be accessed through object attributes, they are stored internally as Datum
objects.
These full Datum
objects can be accessed as items of the parameters
attribute:
pa1.parameters['numRandomShuffles'].value # 50
pa1.parameters['numRandomShuffles'].units # ''
pa1.parameters['numRandomShuffles'].label # numRandomShuffles
pa1.parameters['numRandomShuffles'].description # 'Number of random shuffles'
Alternative ways of registering parameters¶
The registerParameter()
method is flexible in terms of its arguments.
For example, it’s possible to first register a parameter and set its value later:
self.registerParameter('numRandomShuffles', units='',
description='Number of random shuffles')
# ...
self.numRandomShuffles = 50
Here, a label is not set; in this case the label
defaults to the name of the parameter itself.
It’s also possible to provide a Datum
to registerParameter()
:
self.registerParameter('numRandomShuffles',
datum=Datum(50, '', label='shuffles',
description='Number of random shuffles'))
This can be useful when copying a parameter already available as a Datum
.
Storing Extra Measurement Outputs¶
Although metric measurements are strictly scalar values, it can be useful to store additional measurement by-products. By registering them, these measurement by-products are automatically serialized with the measurement and available to the SQUASH dashboard application to make drive rich plots, such as histograms or scatter plots. This additional metadata helps a user understand a scalar metric measurement.
Registering measurement outputs is similar to registering parameters, except that the registerExtra()
method is used.
As an example, the PA1 measurement code (PA1Measurement
) stores the inter-quartile range, RMS and magnitude difference of pairs of stars multiple random samples, along with mean magnitude of each pair of observed stars.
class PA1Measurement(MeasurementBase):
metric = None
value = None
units = 'mmag'
label = 'PA1'
schema = 'pa1-1.0.0'
def __init__(self, numRandomShuffles=50):
MeasurementBase.__init__(self)
self.metric = Metric.fromYaml(self.label)
# register extras
self.registerExtra('rms', units='mmag',
description='Photometric repeatability RMS of '
'stellar pairs for each random sampling')
self.registerExtra('iqr', units='mmag',
description='Photometric repeatability IQR of '
'stellar pairsfor each random sample')
self.registerExtra('magDiff', units='mmag',
description='Difference magnitudes of stellar source pairs'
'for each random sample')
self.registerExtra('magMean', units='mag',
description='Mean magnitude of pairs of stellar '
'sources matched across visits, for '
'each random sample.')
# ... make measurements
# Set values of extras
self.rms = np.array([pa1.rms for pa1 in pa1Samples])
self.iqr = np.array([pa1.iqr for pa1 in pa1Samples])
self.magDiff = np.array([pa1.magDiffs for pa1 in pa1Samples])
self.magMean = np.array([pa1.magMean for pa1 in pa1Samples])
# The scalar metric measurement
self.value = np.mean(self.iqr)
The registerExtra()
method works just like the registerParameter()
method.
Specifically, the value of the extra can be set at registration time.
An extra can also be registered with a pre-made Datum
object.
Accessing and updating the values and Datum objects of measurement extras¶
As with parameters, registering an extra allows the value of the extra to be accessed or updated through a measurement object attribute named after the extra itself (see the above example).
Extras are stored internally as Datum
objects, which can be accessed as items of the extras
attribute.
Following the PA1 measurement example:
pa1 = PA1Measurement()
pa1.extras['rms'].value # == pa1.rms
pa1.extras['rms'].units # 'mmag'
pa1.extras['rms'].label # 'rms'
pa1.extras['rms'].decription # 'Photometric repeatability RMS ...'