Coverage for tests / test_gen3.py: 34%
88 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-06 09:01 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-06 09:01 +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/>.
22import os
23import unittest
24import shutil
25import tempfile
26from cProfile import Profile
27from pstats import Stats
29import numpy as np
31from astro_metadata_translator import ObservationInfo
32from lsst.obs.lsst import (LsstCam, LsstComCam, LsstCamImSim, LsstCamPhoSim,
33 LsstTS8, LsstTS3, LsstUCDCam, Latiss, LsstComCamSim,
34 LsstCamSim, readRawFitsHeader)
36from lsst.daf.butler import (
37 Butler,
38 DatasetType,
39 StorageClassFactory,
40)
42TESTDIR = os.path.abspath(os.path.dirname(__file__))
43DATAROOT = os.path.join(TESTDIR, "data", "input")
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
51class TestInstruments(unittest.TestCase):
53 def setUp(self):
54 self.root = tempfile.mkdtemp(dir=TESTDIR)
55 self.rng = np.random.RandomState(50) # arbitrary deterministic seed
57 @classmethod
58 def setUpClass(cls):
59 if PRINT_PROFILE:
60 cls.profile = Profile()
61 cls.profile.enable()
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)
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()
75 def checkInstrumentWithRegistry(self, cls, testRaw):
77 butler_config = Butler.makeRepo(self.root)
78 with Butler.from_config(butler_config, run="tests") as butler:
79 instrument = cls()
80 scFactory = StorageClassFactory()
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)
89 # Add Instrument, Detector, and PhysicalFilter entries to the
90 # Butler Registry.
91 instrument.register(butler.registry)
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)
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)
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())
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())
137 def testLsstCam(self):
138 testFpath = "lsstCam/raw/2019-03-22/3019032200002/3019032200002-R10-S22-det035.fits"
139 self.checkInstrumentWithRegistry(LsstCam, testFpath)
141 def testComCam(self):
142 testFpath = "comCam/raw/2019-05-30/3019053000001/3019053000001-R22-S00-det000.fits"
143 self.checkInstrumentWithRegistry(LsstComCam, testFpath)
145 def testComCamSim(self):
146 testFpath = "comCamSim/raw/2024-03-21/7024032100720/7024032100720-R22-S11-det004.fits.fz"
147 self.checkInstrumentWithRegistry(LsstComCamSim, testFpath)
149 def testImSim(self):
150 self.checkInstrumentWithRegistry(LsstCamImSim,
151 "imsim/raw/204595/R11/00204595-R11-S20-det042.fits")
153 def testPhoSim(self):
154 self.checkInstrumentWithRegistry(LsstCamPhoSim,
155 "phosim/raw/204595/R11/00204595-R11-S20-det042.fits")
157 def testLsstCamSim(self):
158 testFpath = "lsstCamSim/raw/2024-03-21/7024032100720/7024032100720-R22-S11-det094.fits.fz"
159 self.checkInstrumentWithRegistry(LsstCamSim, testFpath)
161 def testTs8(self):
162 self.checkInstrumentWithRegistry(LsstTS8,
163 "ts8/raw/6006D/201807241028453-RTM-010-S11-det067.fits")
165 def testTs3(self):
166 self.checkInstrumentWithRegistry(LsstTS3,
167 "ts3/raw/2016-07-22/201607220607067-R071-S00-det071.fits")
169 def testUcdCam(self):
170 self.checkInstrumentWithRegistry(LsstUCDCam,
171 "ucd/raw/2023-10-31/2023103100227-R21-S01-det010.fits")
173 def testLatiss(self):
174 self.checkInstrumentWithRegistry(Latiss,
175 "latiss/raw/2018-09-20/3018092000065-det000.fits")
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)
194if __name__ == "__main__": 194 ↛ 195line 194 didn't jump to line 195 because the condition on line 194 was never true
195 unittest.main()