Source code for lsst.validate.base.job
# See COPYRIGHT file at the top of the source tree.
from __future__ import print_function, division
from .jsonmixin import JsonSerializationMixin
from .blob import BlobBase, DeserializedBlob
from .measurement import MeasurementBase, DeserializedMeasurement
__all__ = ['Job']
[docs]class Job(JsonSerializationMixin):
"""A `Job` wraps all measurements and blob metadata associated with a
validation run.
Measurements and blobs can be added both during initialization, or anytime
afterwards with the `register_measurement` and `register_blob` methods.
The `get_measurement` method lets you search for a stored measurement
based on the `Metric` name (and filter or specification name, if the
measurement is dependent on those).
Use the `Job.json` attribute to access a json-serializable `dict` of all
measurements and blobs associated with the `Job`.
A `Job` can only contain measurements against one dataset at a time.
Typically, `Job`\ s are uploaded to SQUASH separately for each tested
dataset.
Parameters
----------
measurements : `list`, optional
List of `MeasurementBase`-derived objects. Additional measurements can
be added with the `register_measurement` method.
blobs : `list`, optional
List of `BlobBase`-derived objects. Additional blobs can be added
with the `register_blob` method.
"""
def __init__(self, measurements=None, blobs=None):
self._measurements = []
self._measurement_ids = set()
self._blobs = []
self._blob_ids = set()
if measurements:
for m in measurements:
self.register_measurement(m)
if blobs:
for b in blobs:
self.register_blob(b)
[docs] def register_measurement(self, m):
"""Add a measurement object to the `Job`.
Registering a measurement also automatically registers all
linked blobs.
Parameters
----------
m : `MeasurementBase`-type object
A measurement object.
"""
assert isinstance(m, MeasurementBase)
if m.identifier not in self._measurement_ids:
self._measurements.append(m)
self._measurement_ids.add(m.identifier)
for name, b in m.blobs.items():
self.register_blob(b)
@property
def measurements(self):
"""Measurement iterator."""
for m in self._measurements:
yield m
[docs] def get_measurement(self, metric_name, spec_name=None, filter_name=None):
"""Get a measurement corresponding to the given criteria.
Parameters
----------
metric_name : `str`
Name of the `Metric` for the requested measurement.
spec_name : `str`, optional
Name of the specification level if the measurement algorithm is
dependent on the specification level of a metric.
filter_name : `str`, optional
Name of the optical filter if the measurement is specific to a
filter.
Returns
-------
measurement : `MeasurementBase`-type object
The single measurement instance that fulfills the search criteria.
Raises
------
RuntimeError
Raised when a measurement cannot be found, either because no such
measurement exists or because the request is ambiguous
(``spec_name`` or ``filter_name`` need to be set).
"""
candidates = [m for m in self._measurements if m.label == metric_name]
if len(candidates) == 1:
candidate = candidates[0]
if spec_name is not None and candidate.spec_name is not None:
assert candidate.spec_name == spec_name
if filter_name is not None and candidate.filter_name is not None:
assert candidate.filter_name == filter_name
return candidate
# Filter by spec_name
if spec_name is not None:
candidates = [m for m in candidates if m.spec_name == spec_name]
if len(candidates) == 1:
candidate = candidates[0]
if filter_name is not None and candidate.filter_name is not None:
assert candidate.filter_name == filter_name
return candidate
# Filter by filter_name
if filter_name is not None:
candidates = [m for m in candidates
if m.filter_name == filter_name]
if len(candidates) == 1:
return candidates[0]
raise RuntimeError('Measurement not found', metric_name, spec_name)
[docs] def register_blob(self, b):
"""Add a blob object to the `Job`.
Parameters
----------
b : `BlobBase`-type object
A blob object.
"""
assert isinstance(b, BlobBase)
if b.identifier not in self._blob_ids:
self._blobs.append(b)
self._blob_ids.add(b.identifier)
@property
def blobs(self):
"""Blob iterator."""
for b in self._blobs:
yield b
@classmethod
[docs] def from_json(cls, json_data):
"""Construct a Job and constituent objects from a JSON dataset.
Parameters
----------
json_data : `dict`
Job JSON object (as produced by `json`).
Returns
-------
job : `Job`-type
Job from JSON.
"""
blobs = [DeserializedBlob.from_json(doc) for doc in json_data['blobs']]
measurements = [
DeserializedMeasurement.from_json(doc,
blobs_json=json_data['blobs'])
for doc in json_data['measurements']]
job = cls(measurements=measurements, blobs=blobs)
return job
@property
def json(self):
"""`Job` data as a JSON-serialiable `dict`."""
doc = JsonSerializationMixin.jsonify_dict({
'measurements': self._measurements,
'blobs': self._blobs})
return doc
@property
def metric_names(self):
"""Names of `Metric`\ s measured in this `Job` (`list`)."""
metric_names = []
for m in self._measurements:
if m.value is not None:
if m.metric.name not in metric_names:
metric_names.append(m.metric.name)
return metric_names
@property
def spec_levels(self):
"""`list` of names of specification levels that are available for
`Metric`\ s measured in this `Job`.
"""
spec_names = []
for m in self._measurements:
for spec in m.metric.specs:
if spec.name not in spec_names:
spec_names.append(spec.name)
return spec_names