Coverage for python/lsst/obs/base/instrument_tests.py : 46%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of obs_base.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
22"""Helpers for writing tests against subclassses of Instrument.
24These are not tests themselves, but can be subclassed (plus unittest.TestCase)
25to get a functional test of an Instrument.
26"""
28import abc
29import dataclasses
30import pkg_resources
31from typing import Set
33from lsst.obs.base import Instrument, FilterDefinitionCollection, FilterDefinition
34from lsst.obs.base.gen2to3 import TranslatorFactory
35from lsst.obs.base.yamlCamera import makeCamera
36from lsst.daf.butler import Registry
37from lsst.daf.butler import RegistryConfig
38from lsst.utils.introspection import get_full_type_name
39from lsst.daf.butler.formatters.yaml import YamlFormatter
41from .utils import createInitialSkyWcsFromBoresight
43DUMMY_FILTER_DEFINITIONS = FilterDefinitionCollection(
44 FilterDefinition(physical_filter="dummy_u", band="u", lambdaEff=0),
45 FilterDefinition(physical_filter="dummy_g", band="g", lambdaEff=0),
46)
49class DummyCamYamlWcsFormatter(YamlFormatter):
50 """Specialist formatter for tests that can make a WCS."""
52 @classmethod
53 def makeRawSkyWcsFromBoresight(cls, boresight, orientation, detector):
54 """Class method to make a raw sky WCS from boresight and detector.
56 This uses the API expected by define-visits. A working example
57 can be found in `FitsRawFormatterBase`.
59 Notes
60 -----
61 This makes no attempt to create a proper WCS from geometry.
62 """
63 return createInitialSkyWcsFromBoresight(boresight, orientation, detector, flipX=False)
66class DummyCam(Instrument):
68 filterDefinitions = DUMMY_FILTER_DEFINITIONS
70 @classmethod
71 def getName(cls):
72 return "DummyCam"
74 def getCamera(self):
75 # Return something that can be indexed by detector number
76 # but also has to support getIdIter.
77 filename = pkg_resources.resource_filename("lsst.obs.base", "test/dummycam.yaml")
78 return makeCamera(filename)
80 def register(self, registry, update=False):
81 """Insert Instrument, physical_filter, and detector entries into a
82 `Registry`.
83 """
84 detector_max = 2
85 dataId = {"instrument": self.getName(), "class_name": get_full_type_name(DummyCam),
86 "detector_max": detector_max}
87 with registry.transaction():
88 registry.syncDimensionData("instrument", dataId, update=update)
89 self._registerFilters(registry, update=update)
90 for d in range(detector_max):
91 registry.syncDimensionData("detector",
92 dict(dataId, id=d, full_name=f"RXX_S0{d}",),
93 update=update)
95 def getRawFormatter(self, dataId):
96 # Docstring inherited fromt Instrument.getRawFormatter.
97 return DummyCamYamlWcsFormatter
99 def writeCuratedCalibrations(self, butler):
100 pass
102 def applyConfigOverrides(self, name, config):
103 pass
105 def makeDataIdTranslatorFactory(self) -> TranslatorFactory:
106 return TranslatorFactory()
109@dataclasses.dataclass
110class InstrumentTestData:
111 """Values to test against in subclasses of `InstrumentTests`.
112 """
114 name: str
115 """The name of the Camera this instrument describes."""
117 nDetectors: int
118 """The number of detectors in the Camera."""
120 firstDetectorName: str
121 """The name of the first detector in the Camera."""
123 physical_filters: Set[str]
124 """A subset of the physical filters should be registered."""
127class InstrumentTests(metaclass=abc.ABCMeta):
128 """Tests of sublcasses of Instrument.
130 TestCase subclasses must derive from this, then `TestCase`, and override
131 ``data`` and ``instrument``.
132 """
134 data = None
135 """`InstrumentTestData` containing the values to test against."""
137 instrument = None
138 """The `~lsst.obs.base.Instrument` to be tested."""
140 def test_name(self):
141 self.assertEqual(self.instrument.getName(), self.data.name)
143 def test_getCamera(self):
144 """Test that getCamera() returns a reasonable Camera definition.
145 """
146 camera = self.instrument.getCamera()
147 self.assertEqual(camera.getName(), self.instrument.getName())
148 self.assertEqual(len(camera), self.data.nDetectors)
149 self.assertEqual(next(iter(camera)).getName(), self.data.firstDetectorName)
151 def test_register(self):
152 """Test that register() sets appropriate Dimensions.
153 """
154 registryConfig = RegistryConfig()
155 registryConfig["db"] = "sqlite://"
156 registry = Registry.createFromConfig(registryConfig)
157 # Check that the registry starts out empty.
158 self.assertFalse(registry.queryDataIds(["instrument"]).toSequence())
159 self.assertFalse(registry.queryDataIds(["detector"]).toSequence())
160 self.assertFalse(registry.queryDataIds(["physical_filter"]).toSequence())
162 # Register the instrument and check that certain dimensions appear.
163 self.instrument.register(registry)
164 instrumentDataIds = registry.queryDataIds(["instrument"]).toSequence()
165 self.assertEqual(len(instrumentDataIds), 1)
166 instrumentNames = {dataId["instrument"] for dataId in instrumentDataIds}
167 self.assertEqual(instrumentNames, {self.data.name})
168 detectorDataIds = registry.queryDataIds(["detector"]).expanded().toSequence()
169 self.assertEqual(len(detectorDataIds), self.data.nDetectors)
170 detectorNames = {dataId.records["detector"].full_name for dataId in detectorDataIds}
171 self.assertIn(self.data.firstDetectorName, detectorNames)
172 physicalFilterDataIds = registry.queryDataIds(["physical_filter"]).toSequence()
173 filterNames = {dataId['physical_filter'] for dataId in physicalFilterDataIds}
174 self.assertGreaterEqual(filterNames, self.data.physical_filters)
176 # Check that the instrument class can be retrieved.
177 registeredInstrument = Instrument.fromName(self.instrument.getName(), registry)
178 self.assertEqual(type(registeredInstrument), type(self.instrument))
180 # Check that re-registration is not an error.
181 self.instrument.register(registry)
183 def testMakeTranslatorFactory(self):
184 factory = self.instrument.makeDataIdTranslatorFactory()
185 self.assertIsInstance(factory, TranslatorFactory)
186 str(factory) # Just make sure this doesn't raise.