Coverage for python / lsst / obs / base / tests.py: 27%

41 statements  

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

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 <https://www.gnu.org/licenses/>. 

21 

22""" 

23Test utilities for obs_base and concrete obs* packages. 

24""" 

25 

26from __future__ import annotations 

27 

28__all__ = ( 

29 "ObsTests", 

30 "make_ramp_array", 

31 "make_ramp_exposure_trimmed", 

32 "make_ramp_exposure_untrimmed", 

33) 

34 

35import logging 

36from typing import TYPE_CHECKING, Any 

37 

38import numpy as np 

39 

40from lsst.afw.cameraGeom.utils import calcRawCcdBBox 

41from lsst.afw.image import Exposure 

42 

43from . import butler_tests, camera_tests 

44 

45if TYPE_CHECKING: 

46 import lsst.afw.cameraGeom 

47 import lsst.afw.geom 

48 import lsst.afw.image 

49 import lsst.daf.butler 

50 import lsst.geom 

51 

52 

53class ObsTests(butler_tests.ButlerGetTests, camera_tests.CameraTests): 

54 """Aggregator class for all of the obs_* test classes. 

55 

56 Inherit from this class, then lsst.utils.tests.TestCase, in that order. 

57 

58 Examples 

59 -------- 

60 Example subclass: 

61 

62 .. code-block:: python 

63 

64 class TestObs( 

65 lsst.obs.base.tests.ObsTests, lsst.utils.tests.TestCase 

66 ): 

67 def setUp(self): 

68 self.setUp_tests(...) 

69 self.setUp_butler_get(...) 

70 self.setUp_camera(...) 

71 

72 Notes 

73 ----- 

74 The intention is for each obs package to have a single test class that 

75 inherits from this collector class, thus "automatically" getting all new 

76 tests. If those tests require setup that isn't defined in a given obs 

77 package, that obs package will be broken until updated. This is 

78 intentional, as a way to prevent obs packages from falling behind out of 

79 neglect. 

80 """ 

81 

82 def setUp_tests(self, butler: lsst.daf.butler.Butler, dataIds: dict[str, Any]) -> Any: 

83 """Set up the necessary shared variables used by multiple tests. 

84 

85 Parameters 

86 ---------- 

87 butler : `lsst.daf.butler.Butler` 

88 A butler object, instantiated on the testdata repository for the 

89 obs package being tested. 

90 dataIds : `dict` 

91 dictionary of (exposure name): (dataId of that exposure in the 

92 testdata repository), with unittest.SkipTest as the value for any 

93 exposures you do not have/do not want to test. It must contain a 

94 valid 'raw' dataId, in addition to 'bias','flat','dark', which may 

95 be set to SkipTest. For example:: 

96 

97 self.dataIds = { 

98 "raw": {"visit": 1, "filter": "g"}, 

99 "bias": {"visit": 1}, 

100 "flat": {"visit": 1}, 

101 "dark": unittest.SkipTest, 

102 } 

103 """ 

104 self.butler = butler 

105 self.dataIds = dataIds 

106 self.log = logging.getLogger(__name__) 

107 

108 def tearDown(self) -> None: 

109 del self.butler 

110 super().tearDown() # type: ignore[misc] 

111 

112 

113def make_ramp_array(bbox: lsst.geom.Box2I, pedestal: int) -> tuple[np.ndarray, int]: 

114 """Make a 2-d ramp array. 

115 

116 Parameters 

117 ---------- 

118 bbox : `lsst.geom.Box2I` 

119 Bounding box for the array. 

120 pedestal : `int` 

121 Minimum value for the ramp. 

122 

123 Returns 

124 ------- 

125 ramp : `numpy.ndarray` 

126 A 2-d array with shape ``(bbox.getHeight(), bbox.getWidth())``. 

127 end : `int` 

128 One past the maximum value in the ramp (for use as the 

129 pedestal for another box). 

130 """ 

131 end = pedestal + bbox.getArea() 

132 return np.arange(pedestal, end).reshape(bbox.getHeight(), bbox.getWidth()), end 

133 

134 

135def make_ramp_exposure_untrimmed( 

136 detector: lsst.afw.cameraGeom.Detector, dtype: np.dtype | None = None 

137) -> Exposure: 

138 """Create an untrimmed, assembled exposure with different ramps for 

139 each sub-amplifier region. 

140 

141 Parameters 

142 ---------- 

143 detector : `lsst.afw.cameraGeom.Detector` 

144 Detector object that the new exposure should match. Must have all amp 

145 flips and offsets set to False/zero (i.e. represent an already- 

146 assembled image). 

147 dtype : `numpy.dtype`, optional 

148 Type of the new exposure. Defaults to ``int32``. 

149 

150 Returns 

151 ------- 

152 exposure : `lsst.afw.image.Exposure` 

153 New exposure with the given detector attached. 

154 """ 

155 if dtype is None: 

156 dtype = np.dtype(np.int32) 

157 ramp_exposure = Exposure(calcRawCcdBBox(detector), dtype=np.dtype(dtype)) 

158 ramp_exposure.setDetector(detector) 

159 pedestal = 0 

160 for amp in detector: 

161 for name in ("HorizontalOverscan", "VerticalOverscan", "Prescan", "Data"): 

162 bbox = getattr(amp, f"getRaw{name}BBox")() 

163 ramp, pedestal = make_ramp_array(bbox, pedestal) 

164 ramp_exposure.image[bbox].array[:, :] = ramp 

165 return ramp_exposure 

166 

167 

168def make_ramp_exposure_trimmed( 

169 detector: lsst.afw.cameraGeom.Detector, dtype: np.dtype | None = None 

170) -> Exposure: 

171 """Create a trimmed, assembled exposure with different ramps for 

172 each amplifier region. 

173 

174 Parameters 

175 ---------- 

176 detector : `lsst.afw.cameraGeom.Detector` 

177 Detector object that the new exposure should match. 

178 dtype : `numpy.dtype`, optional 

179 Type of the new exposure. Defaults to ``int32``. 

180 

181 Returns 

182 ------- 

183 exposure : `lsst.afw.image.Exposure` 

184 New exposure with the given detector attached. 

185 """ 

186 if dtype is None: 

187 dtype = np.dtype(np.int32) 

188 ramp_exposure = Exposure(detector.getBBox(), dtype=np.dtype(dtype)) 

189 ramp_exposure.setDetector(detector) 

190 pedestal = 0 

191 for amp in detector: 

192 ramp, pedestal = make_ramp_array(amp.getBBox(), pedestal) 

193 ramp_exposure.image[amp.getBBox()].array[:, :] = ramp 

194 return ramp_exposure