Coverage for tests / test_gen3.py: 34%

88 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-22 09:22 +0000

1# This file is part of obs_lsst. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://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 

22import os 

23import unittest 

24import shutil 

25import tempfile 

26from cProfile import Profile 

27from pstats import Stats 

28 

29import numpy as np 

30 

31from astro_metadata_translator import ObservationInfo 

32from lsst.obs.lsst import (LsstCam, LsstComCam, LsstCamImSim, LsstCamPhoSim, 

33 LsstTS8, LsstTS3, LsstUCDCam, Latiss, LsstComCamSim, 

34 LsstCamSim, readRawFitsHeader) 

35 

36from lsst.daf.butler import ( 

37 Butler, 

38 DatasetType, 

39 StorageClassFactory, 

40) 

41 

42TESTDIR = os.path.abspath(os.path.dirname(__file__)) 

43DATAROOT = os.path.join(TESTDIR, "data", "input") 

44 

45# This test is unfortunately slow; leave a profiling option in in case we want 

46# to improve it later. Initial version is about 60% YamlCamera construction 

47# (!) and 40% SQLite operations inside Butler. 

48PRINT_PROFILE = False 

49 

50 

51class TestInstruments(unittest.TestCase): 

52 

53 def setUp(self): 

54 self.root = tempfile.mkdtemp(dir=TESTDIR) 

55 self.rng = np.random.RandomState(50) # arbitrary deterministic seed 

56 

57 @classmethod 

58 def setUpClass(cls): 

59 if PRINT_PROFILE: 

60 cls.profile = Profile() 

61 cls.profile.enable() 

62 

63 def tearDown(self): 

64 if self.root is not None and os.path.exists(self.root): 

65 shutil.rmtree(self.root, ignore_errors=True) 

66 

67 @classmethod 

68 def tearDownClass(cls): 

69 if PRINT_PROFILE: 

70 stats = Stats(cls.profile) 

71 stats.strip_dirs() 

72 stats.sort_stats("cumtime") 

73 stats.print_stats() 

74 

75 def checkInstrumentWithRegistry(self, cls, testRaw): 

76 

77 butler_config = Butler.makeRepo(self.root) 

78 with Butler.from_config(butler_config, run="tests") as butler: 

79 instrument = cls() 

80 scFactory = StorageClassFactory() 

81 

82 # Check instrument class and metadata translator agree on 

83 # instrument name, using readRawFitsHeader to read the metadata. 

84 filename = os.path.join(DATAROOT, testRaw) 

85 md = readRawFitsHeader(filename, translator_class=cls.translatorClass) 

86 obsInfo = ObservationInfo(md, translator_class=cls.translatorClass, filename=filename) 

87 self.assertEqual(instrument.getName(), obsInfo.instrument) 

88 

89 # Add Instrument, Detector, and PhysicalFilter entries to the 

90 # Butler Registry. 

91 instrument.register(butler.registry) 

92 

93 # Define a DatasetType for the cameraGeom.Camera, which can be 

94 # accessed just by identifying its Instrument. 

95 # A real-world Camera DatasetType should be identified by a 

96 # validity range as well. 

97 cameraDatasetType = DatasetType("camera", dimensions=["instrument"], 

98 storageClass=scFactory.getStorageClass("Camera"), 

99 universe=butler.dimensions) 

100 butler.registry.registerDatasetType(cameraDatasetType) 

101 

102 # Define a DatasetType for cameraGeom.Detectors, which can be 

103 # accessed by identifying its Instrument and (Butler) Detector. 

104 # A real-world Detector DatasetType probably doesn't need to exist, 

105 # as it would just duplicate information in the Camera, and 

106 # reading a full Camera just to get a single Detector should be 

107 # plenty efficient. 

108 detectorDatasetType = DatasetType("detector", dimensions=["instrument", "detector"], 

109 storageClass=scFactory.getStorageClass("Detector"), 

110 universe=butler.dimensions) 

111 butler.registry.registerDatasetType(detectorDatasetType) 

112 

113 # Put and get the Camera. 

114 dataId = dict(instrument=instrument.instrument) 

115 butler.put(instrument.getCamera(), "camera", dataId=dataId) 

116 camera = butler.get("camera", dataId) 

117 # Full camera comparisons are *slow*; just compare names. 

118 self.assertEqual(instrument.getCamera().getName(), camera.getName()) 

119 

120 # Put and get a random subset of the Detectors. 

121 allDetectors = list(instrument.getCamera()) 

122 numDetectors = min(3, len(allDetectors)) 

123 someDetectors = [allDetectors[i] for i in self.rng.choice(len(allDetectors), 

124 size=numDetectors, replace=False)] 

125 for cameraGeomDetector in someDetectors: 

126 # Right now we only support integer detector IDs in data IDs; 

127 # support for detector names and groups (i.e. rafts) is 

128 # definitely planned but not yet implemented. 

129 dataId = dict(instrument=instrument.instrument, detector=cameraGeomDetector.getId()) 

130 butler.put(cameraGeomDetector, "detector", dataId=dataId) 

131 cameraGeomDetector2 = butler.get("detector", dataId=dataId) 

132 # Full detector comparisons are *slow*; just compare names and 

133 # serials. 

134 self.assertEqual(cameraGeomDetector.getName(), cameraGeomDetector2.getName()) 

135 self.assertEqual(cameraGeomDetector.getSerial(), cameraGeomDetector2.getSerial()) 

136 

137 def testLsstCam(self): 

138 testFpath = "lsstCam/raw/2019-03-22/3019032200002/3019032200002-R10-S22-det035.fits" 

139 self.checkInstrumentWithRegistry(LsstCam, testFpath) 

140 

141 def testComCam(self): 

142 testFpath = "comCam/raw/2019-05-30/3019053000001/3019053000001-R22-S00-det000.fits" 

143 self.checkInstrumentWithRegistry(LsstComCam, testFpath) 

144 

145 def testComCamSim(self): 

146 testFpath = "comCamSim/raw/2024-03-21/7024032100720/7024032100720-R22-S11-det004.fits.fz" 

147 self.checkInstrumentWithRegistry(LsstComCamSim, testFpath) 

148 

149 def testImSim(self): 

150 self.checkInstrumentWithRegistry(LsstCamImSim, 

151 "imsim/raw/204595/R11/00204595-R11-S20-det042.fits") 

152 

153 def testPhoSim(self): 

154 self.checkInstrumentWithRegistry(LsstCamPhoSim, 

155 "phosim/raw/204595/R11/00204595-R11-S20-det042.fits") 

156 

157 def testLsstCamSim(self): 

158 testFpath = "lsstCamSim/raw/2024-03-21/7024032100720/7024032100720-R22-S11-det094.fits.fz" 

159 self.checkInstrumentWithRegistry(LsstCamSim, testFpath) 

160 

161 def testTs8(self): 

162 self.checkInstrumentWithRegistry(LsstTS8, 

163 "ts8/raw/6006D/201807241028453-RTM-010-S11-det067.fits") 

164 

165 def testTs3(self): 

166 self.checkInstrumentWithRegistry(LsstTS3, 

167 "ts3/raw/2016-07-22/201607220607067-R071-S00-det071.fits") 

168 

169 def testUcdCam(self): 

170 self.checkInstrumentWithRegistry(LsstUCDCam, 

171 "ucd/raw/2023-10-31/2023103100227-R21-S01-det010.fits") 

172 

173 def testLatiss(self): 

174 self.checkInstrumentWithRegistry(Latiss, 

175 "latiss/raw/2018-09-20/3018092000065-det000.fits") 

176 

177 def test_exposure_max(self): 

178 # Ensure that the exposure max value does not change. 

179 for cls, exp_max in ( 

180 (LsstCam, 7050123199999), 

181 (LsstComCam, 7050123199999), 

182 (LsstCamImSim, 9999999), 

183 (LsstCamPhoSim, 9999999), 

184 (LsstTS8, 205012312359999), 

185 (LsstTS3, 205012312359999), 

186 (LsstUCDCam, 7050123199999), 

187 (Latiss, 7050123199999), 

188 (LsstComCamSim, 7050123199999), 

189 (LsstCamSim, 7050123199999), 

190 ): 

191 self.assertEqual(cls.translatorClass.max_exposure_id(), exp_max) 

192 

193 

194if __name__ == "__main__": 194 ↛ 195line 194 didn't jump to line 195 because the condition on line 194 was never true

195 unittest.main()