Source code for lsst.validate.base.spec

# See COPYRIGHT file at the top of the source tree.
from __future__ import print_function, division

import astropy.units as u

from .jsonmixin import JsonSerializationMixin
from .datum import Datum, QuantityAttributeMixin


__all__ = ['Specification']


[docs]class Specification(QuantityAttributeMixin, JsonSerializationMixin): """A specification level, or threshold, associated with a `Metric`. Parameters ---------- name : `str` Name of the specification level for a metric. LPM-17, for example, uses ``'design'``, ``'minimum'`` and ``'stretch'`` terminology. quantity : `astropy.units.Quantity`, `float`, or `int` The specification threshold level. unit : `str`, optional `astropy.units.Unit`-compatible `str` describing the units of ``value`` (only necessary if `quantity` is a `float`). An empty string (``''``) describes a unitless quantity. filter_names : `list`, optional A list of optical filter names that this specification applies to. Set only if the specification level is dependent on the filter. dependencies : `dict`, optional A dictionary of named `Datum` values that must be known when making a measurement against a specification level. Dependencies can be accessed as attributes of the specification object. The names of class attributes match keys in `dependencies`. """ name = None """Name of the specification level for a metric. LPM-17, for example, uses ``'design'``, ``'minimum'`` and ``'stretch'`` terminology. """ quantity = None """The specification threshold level (`astropy.units.Quantity`).""" filter_names = None """`list` of names of optical filters that this Specification level applies to. Default is `None` if the `Specification` is filter-independent. """ dependencies = None """`dict` of named `Datum` values that must be known when making a measurement against a specification level. Dependencies can also be accessed as attributes of the `Specification` object. The names of class attributes match keys in `dependencies`. """ def __init__(self, name, quantity, unit=None, filter_names=None, dependencies=None): self.name = name if unit is not None: self.quantity = quantity * u.Unit(unit) else: self.quantity = quantity self.filter_names = filter_names if dependencies: self.dependencies = dependencies else: self.dependencies = {} def __getattr__(self, key): """Access dependencies with keys as attributes.""" if key in self.dependencies: return self.dependencies[key] else: raise AttributeError("%r object has no attribute %r" % (self.__class__, key)) @property def datum(self): """Representation of this `Specification` as a `Datum`.""" return Datum(self.quantity, label=self.name) @classmethod
[docs] def from_json(cls, json_data): """Construct a Specification from a JSON document. Parameters ---------- json_data : `dict` Specification JSON object. Returns ------- specification : `Specification` Specification from JSON. """ q = Datum._rebuild_quantity(json_data['value'], json_data['unit']) deps = {k: Datum.from_json(v) for k, v in json_data['dependencies'].items()} s = cls(name=json_data['name'], quantity=q, filter_names=json_data['filter_names'], dependencies=deps) return s
@property def json(self): """`dict` that can be serialized as semantic JSON, compatible with the SQUASH metric service. """ if isinstance(self.quantity, u.Quantity): v = self.quantity.value else: v = self.quantity return JsonSerializationMixin.jsonify_dict({ 'name': self.name, 'value': v, 'unit': self.unit_str, 'filter_names': self.filter_names, 'dependencies': self.dependencies})