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

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
32from lsst.obs.base import Instrument, FilterDefinitionCollection, FilterDefinition
33from lsst.obs.base.gen2to3 import TranslatorFactory
34from lsst.obs.base.yamlCamera import makeCamera
35from lsst.daf.butler import Registry
36from lsst.daf.butler import RegistryConfig
37from lsst.daf.butler.core.utils import getFullTypeName
38from lsst.daf.butler.formatters.yaml import YamlFormatter
40from .utils import createInitialSkyWcsFromBoresight
42DUMMY_FILTER_DEFINITIONS = FilterDefinitionCollection(
43 FilterDefinition(physical_filter="dummy_u", band="u", lambdaEff=0),
44 FilterDefinition(physical_filter="dummy_g", band="g", lambdaEff=0),
45)
48class DummyCamYamlWcsFormatter(YamlFormatter):
49 """Specialist formatter for tests that can make a WCS."""
51 @classmethod
52 def makeRawSkyWcsFromBoresight(cls, boresight, orientation, detector):
53 """Class method to make a raw sky WCS from boresight and detector.
55 This uses the API expected by define-visits. A working example
56 can be found in `FitsRawFormatterBase`.
58 Notes
59 -----
60 This makes no attempt to create a proper WCS from geometry.
61 """
62 return createInitialSkyWcsFromBoresight(boresight, orientation, detector, flipX=False)
65class DummyCam(Instrument):
67 filterDefinitions = DUMMY_FILTER_DEFINITIONS
69 @classmethod
70 def getName(cls):
71 return "DummyCam"
73 def getCamera(self):
74 # Return something that can be indexed by detector number
75 # but also has to support getIdIter.
76 filename = pkg_resources.resource_filename("lsst.obs.base", "test/dummycam.yaml")
77 return makeCamera(filename)
79 def register(self, registry):
80 """Insert Instrument, physical_filter, and detector entries into a
81 `Registry`.
82 """
83 detector_max = 2
84 dataId = {"instrument": self.getName(), "class_name": getFullTypeName(DummyCam),
85 "detector_max": detector_max}
86 with registry.transaction():
87 registry.syncDimensionData("instrument", dataId)
88 self._registerFilters(registry)
89 for d in range(detector_max):
90 registry.syncDimensionData("detector",
91 dict(dataId, id=d, full_name=f"RXX_S0{d}"))
93 def getRawFormatter(self, dataId):
94 # Docstring inherited fromt Instrument.getRawFormatter.
95 return DummyCamYamlWcsFormatter
97 def writeCuratedCalibrations(self, butler):
98 pass
100 def applyConfigOverrides(self, name, config):
101 pass
103 def makeDataIdTranslatorFactory(self) -> TranslatorFactory:
104 return TranslatorFactory()
107@dataclasses.dataclass
108class InstrumentTestData:
109 """Values to test against in sublcasses of `InstrumentTests`.
110 """
112 name: str
113 """The name of the Camera this instrument describes."""
115 nDetectors: int
116 """The number of detectors in the Camera."""
118 firstDetectorName: str
119 """The name of the first detector in the Camera."""
121 physical_filters: {str}
122 """A subset of the physical filters should be registered."""
125class InstrumentTests(metaclass=abc.ABCMeta):
126 """Tests of sublcasses of Instrument.
128 TestCase subclasses must derive from this, then `TestCase`, and override
129 ``data`` and ``instrument``.
130 """
132 data = None
133 """`InstrumentTestData` containing the values to test against."""
135 instrument = None
136 """The `~lsst.obs.base.Instrument` to be tested."""
138 def test_name(self):
139 self.assertEqual(self.instrument.getName(), self.data.name)
141 def test_getCamera(self):
142 """Test that getCamera() returns a reasonable Camera definition.
143 """
144 camera = self.instrument.getCamera()
145 self.assertEqual(camera.getName(), self.instrument.getName())
146 self.assertEqual(len(camera), self.data.nDetectors)
147 self.assertEqual(next(iter(camera)).getName(), self.data.firstDetectorName)
149 def test_register(self):
150 """Test that register() sets appropriate Dimensions.
151 """
152 registryConfig = RegistryConfig()
153 registryConfig["db"] = "sqlite://"
154 registry = Registry.createFromConfig(registryConfig)
155 # Check that the registry starts out empty.
156 self.assertFalse(registry.queryDataIds(["instrument"]).toSequence())
157 self.assertFalse(registry.queryDataIds(["detector"]).toSequence())
158 self.assertFalse(registry.queryDataIds(["physical_filter"]).toSequence())
160 # Register the instrument and check that certain dimensions appear.
161 self.instrument.register(registry)
162 instrumentDataIds = registry.queryDataIds(["instrument"]).toSequence()
163 self.assertEqual(len(instrumentDataIds), 1)
164 instrumentNames = {dataId["instrument"] for dataId in instrumentDataIds}
165 self.assertEqual(instrumentNames, {self.data.name})
166 detectorDataIds = registry.queryDataIds(["detector"]).expanded().toSequence()
167 self.assertEqual(len(detectorDataIds), self.data.nDetectors)
168 detectorNames = {dataId.records["detector"].full_name for dataId in detectorDataIds}
169 self.assertIn(self.data.firstDetectorName, detectorNames)
170 physicalFilterDataIds = registry.queryDataIds(["physical_filter"]).toSequence()
171 filterNames = {dataId['physical_filter'] for dataId in physicalFilterDataIds}
172 self.assertGreaterEqual(filterNames, self.data.physical_filters)
174 # Check that the instrument class can be retrieved.
175 registeredInstrument = Instrument.fromName(self.instrument.getName(), registry)
176 self.assertEqual(type(registeredInstrument), type(self.instrument))
178 # Check that re-registration is not an error.
179 self.instrument.register(registry)
181 def testMakeTranslatorFactory(self):
182 factory = self.instrument.makeDataIdTranslatorFactory()
183 self.assertIsInstance(factory, TranslatorFactory)
184 str(factory) # Just make sure this doesn't raise.