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 

31 

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 

39 

40from .utils import createInitialSkyWcsFromBoresight 

41 

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) 

46 

47 

48class DummyCamYamlWcsFormatter(YamlFormatter): 

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

50 

51 @classmethod 

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

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

54 

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

56 can be found in `FitsRawFormatterBase`. 

57 

58 Notes 

59 ----- 

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

61 """ 

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

63 

64 

65class DummyCam(Instrument): 

66 

67 filterDefinitions = DUMMY_FILTER_DEFINITIONS 

68 

69 @classmethod 

70 def getName(cls): 

71 return "DummyCam" 

72 

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) 

78 

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

92 

93 def getRawFormatter(self, dataId): 

94 # Docstring inherited fromt Instrument.getRawFormatter. 

95 return DummyCamYamlWcsFormatter 

96 

97 def writeCuratedCalibrations(self, butler): 

98 pass 

99 

100 def applyConfigOverrides(self, name, config): 

101 pass 

102 

103 def makeDataIdTranslatorFactory(self) -> TranslatorFactory: 

104 return TranslatorFactory() 

105 

106 

107@dataclasses.dataclass 

108class InstrumentTestData: 

109 """Values to test against in sublcasses of `InstrumentTests`. 

110 """ 

111 

112 name: str 

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

114 

115 nDetectors: int 

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

117 

118 firstDetectorName: str 

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

120 

121 physical_filters: {str} 

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

123 

124 

125class InstrumentTests(metaclass=abc.ABCMeta): 

126 """Tests of sublcasses of Instrument. 

127 

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

129 ``data`` and ``instrument``. 

130 """ 

131 

132 data = None 

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

134 

135 instrument = None 

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

137 

138 def test_name(self): 

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

140 

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) 

148 

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

159 

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) 

173 

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

177 

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

179 self.instrument.register(registry) 

180 

181 def testMakeTranslatorFactory(self): 

182 factory = self.instrument.makeDataIdTranslatorFactory() 

183 self.assertIsInstance(factory, TranslatorFactory) 

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