Hide keyboard shortcuts

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/>. 

21 

22"""Helpers for writing tests against subclassses of Instrument. 

23 

24These are not tests themselves, but can be subclassed (plus unittest.TestCase) 

25to get a functional test of an Instrument. 

26""" 

27 

28import abc 

29import dataclasses 

30import pkg_resources 

31from typing import Set 

32 

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 

40 

41from .utils import createInitialSkyWcsFromBoresight 

42 

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) 

47 

48 

49class DummyCamYamlWcsFormatter(YamlFormatter): 

50 """Specialist formatter for tests that can make a WCS.""" 

51 

52 @classmethod 

53 def makeRawSkyWcsFromBoresight(cls, boresight, orientation, detector): 

54 """Class method to make a raw sky WCS from boresight and detector. 

55 

56 This uses the API expected by define-visits. A working example 

57 can be found in `FitsRawFormatterBase`. 

58 

59 Notes 

60 ----- 

61 This makes no attempt to create a proper WCS from geometry. 

62 """ 

63 return createInitialSkyWcsFromBoresight(boresight, orientation, detector, flipX=False) 

64 

65 

66class DummyCam(Instrument): 

67 

68 filterDefinitions = DUMMY_FILTER_DEFINITIONS 

69 

70 @classmethod 

71 def getName(cls): 

72 return "DummyCam" 

73 

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) 

79 

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}")) 

93 

94 def getRawFormatter(self, dataId): 

95 # Docstring inherited fromt Instrument.getRawFormatter. 

96 return DummyCamYamlWcsFormatter 

97 

98 def writeCuratedCalibrations(self, butler): 

99 pass 

100 

101 def applyConfigOverrides(self, name, config): 

102 pass 

103 

104 def makeDataIdTranslatorFactory(self) -> TranslatorFactory: 

105 return TranslatorFactory() 

106 

107 

108@dataclasses.dataclass 

109class InstrumentTestData: 

110 """Values to test against in subclasses of `InstrumentTests`. 

111 """ 

112 

113 name: str 

114 """The name of the Camera this instrument describes.""" 

115 

116 nDetectors: int 

117 """The number of detectors in the Camera.""" 

118 

119 firstDetectorName: str 

120 """The name of the first detector in the Camera.""" 

121 

122 physical_filters: Set[str] 

123 """A subset of the physical filters should be registered.""" 

124 

125 

126class InstrumentTests(metaclass=abc.ABCMeta): 

127 """Tests of sublcasses of Instrument. 

128 

129 TestCase subclasses must derive from this, then `TestCase`, and override 

130 ``data`` and ``instrument``. 

131 """ 

132 

133 data = None 

134 """`InstrumentTestData` containing the values to test against.""" 

135 

136 instrument = None 

137 """The `~lsst.obs.base.Instrument` to be tested.""" 

138 

139 def test_name(self): 

140 self.assertEqual(self.instrument.getName(), self.data.name) 

141 

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) 

149 

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()) 

160 

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) 

174 

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)) 

178 

179 # Check that re-registration is not an error. 

180 self.instrument.register(registry) 

181 

182 def testMakeTranslatorFactory(self): 

183 factory = self.instrument.makeDataIdTranslatorFactory() 

184 self.assertIsInstance(factory, TranslatorFactory) 

185 str(factory) # Just make sure this doesn't raise.