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.daf.butler.core.utils import getFullTypeName
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):
81 """Insert Instrument, physical_filter, and detector entries into a
82 `Registry`.
83 """
84 detector_max = 2
85 dataId = {"instrument": self.getName(), "class_name": getFullTypeName(DummyCam),
86 "detector_max": detector_max}
87 with registry.transaction():
88 registry.syncDimensionData("instrument", dataId)
89 self._registerFilters(registry)
90 for d in range(detector_max):
91 registry.syncDimensionData("detector",
92 dict(dataId, id=d, full_name=f"RXX_S0{d}"))
94 def getRawFormatter(self, dataId):
95 # Docstring inherited fromt Instrument.getRawFormatter.
96 return DummyCamYamlWcsFormatter
98 def writeCuratedCalibrations(self, butler):
99 pass
101 def applyConfigOverrides(self, name, config):
102 pass
104 def makeDataIdTranslatorFactory(self) -> TranslatorFactory:
105 return TranslatorFactory()
108@dataclasses.dataclass
109class InstrumentTestData:
110 """Values to test against in subclasses of `InstrumentTests`.
111 """
113 name: str
114 """The name of the Camera this instrument describes."""
116 nDetectors: int
117 """The number of detectors in the Camera."""
119 firstDetectorName: str
120 """The name of the first detector in the Camera."""
122 physical_filters: Set[str]
123 """A subset of the physical filters should be registered."""
126class InstrumentTests(metaclass=abc.ABCMeta):
127 """Tests of sublcasses of Instrument.
129 TestCase subclasses must derive from this, then `TestCase`, and override
130 ``data`` and ``instrument``.
131 """
133 data = None
134 """`InstrumentTestData` containing the values to test against."""
136 instrument = None
137 """The `~lsst.obs.base.Instrument` to be tested."""
139 def test_name(self):
140 self.assertEqual(self.instrument.getName(), self.data.name)
142 def test_getCamera(self):
143 """Test that getCamera() returns a reasonable Camera definition.
144 """
145 camera = self.instrument.getCamera()
146 self.assertEqual(camera.getName(), self.instrument.getName())
147 self.assertEqual(len(camera), self.data.nDetectors)
148 self.assertEqual(next(iter(camera)).getName(), self.data.firstDetectorName)
150 def test_register(self):
151 """Test that register() sets appropriate Dimensions.
152 """
153 registryConfig = RegistryConfig()
154 registryConfig["db"] = "sqlite://"
155 registry = Registry.createFromConfig(registryConfig)
156 # Check that the registry starts out empty.
157 self.assertFalse(registry.queryDataIds(["instrument"]).toSequence())
158 self.assertFalse(registry.queryDataIds(["detector"]).toSequence())
159 self.assertFalse(registry.queryDataIds(["physical_filter"]).toSequence())
161 # Register the instrument and check that certain dimensions appear.
162 self.instrument.register(registry)
163 instrumentDataIds = registry.queryDataIds(["instrument"]).toSequence()
164 self.assertEqual(len(instrumentDataIds), 1)
165 instrumentNames = {dataId["instrument"] for dataId in instrumentDataIds}
166 self.assertEqual(instrumentNames, {self.data.name})
167 detectorDataIds = registry.queryDataIds(["detector"]).expanded().toSequence()
168 self.assertEqual(len(detectorDataIds), self.data.nDetectors)
169 detectorNames = {dataId.records["detector"].full_name for dataId in detectorDataIds}
170 self.assertIn(self.data.firstDetectorName, detectorNames)
171 physicalFilterDataIds = registry.queryDataIds(["physical_filter"]).toSequence()
172 filterNames = {dataId['physical_filter'] for dataId in physicalFilterDataIds}
173 self.assertGreaterEqual(filterNames, self.data.physical_filters)
175 # Check that the instrument class can be retrieved.
176 registeredInstrument = Instrument.fromName(self.instrument.getName(), registry)
177 self.assertEqual(type(registeredInstrument), type(self.instrument))
179 # Check that re-registration is not an error.
180 self.instrument.register(registry)
182 def testMakeTranslatorFactory(self):
183 factory = self.instrument.makeDataIdTranslatorFactory()
184 self.assertIsInstance(factory, TranslatorFactory)
185 str(factory) # Just make sure this doesn't raise.