Coverage for tests / test_stamps.py: 24%

127 statements  

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

1# This file is part of meas_algorithms. 

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 

22import unittest 

23import numpy as np 

24import tempfile 

25import os 

26 

27from lsst.meas.algorithms import stamps 

28from lsst.afw import image as afwImage 

29from lsst.afw.geom.testUtils import TransformTestBaseClass 

30from lsst.daf.base import PropertyList 

31import lsst.geom as geom 

32import lsst.afw.geom.transformFactory as tF 

33import lsst.utils.tests 

34 

35_RNG = np.random.Generator(np.random.MT19937(5)) 

36 

37 

38def make_stamps(n_stamps=3, use_archive=False): 

39 stampSize = 25 

40 # create dummy stamp stamps 

41 stampImages = [afwImage.MaskedImageF(stampSize, stampSize) 

42 for _ in range(n_stamps)] 

43 for stampIm in stampImages: 

44 stampImArray = stampIm.image.array 

45 stampImArray += _RNG.random((stampSize, stampSize)) 

46 stampMaskArray = stampIm.mask.array 

47 stampMaskArray += 10 

48 stampVarArray = stampIm.variance.array 

49 stampVarArray += 1000. 

50 ras = _RNG.random(n_stamps)*360. 

51 decs = _RNG.random(n_stamps)*180 - 90 

52 archive_elements = [tF.makeTransform(geom.AffineTransform(_RNG.random(2))) if use_archive else None 

53 for _ in range(n_stamps)] 

54 

55 metadata = PropertyList() 

56 metadata['RA_DEG'] = ras 

57 metadata['DEC_DEG'] = decs 

58 

59 stamp_list = [stamps.Stamp(stamp_im=stampIm, 

60 position=geom.SpherePoint(geom.Angle(ra, geom.degrees), 

61 geom.Angle(dec, geom.degrees)), 

62 archive_element=ae, 

63 metadata=metadata) 

64 for stampIm, ra, dec, ae in zip(stampImages, ras, decs, archive_elements)] 

65 

66 return stamps.Stamps(stamp_list, metadata=metadata, use_archive=True) 

67 

68 

69class TransformTestClass(TransformTestBaseClass): 

70 """Class for unit tests for Transform<X>To<Y> within meas_algorithms. 

71 

72 Inherits from `lsst.afw.geom.testUtils.TransformTestBaseClass`. 

73 """ 

74 def getTestDir(self): 

75 """Returns the test directory of the `meas_algorithms` package. 

76 

77 If a similar test is needed in another package, inherit from 

78 `TransformTestBaseClass` and overwrite this method; see the docstrings 

79 in the parent class. 

80 """ 

81 return os.path.join(lsst.utils.getPackageDir("meas_algorithms"), "tests") 

82 

83 

84class StampsBaseTestCase(lsst.utils.tests.TestCase): 

85 """Test StampsBase. 

86 """ 

87 def testReadFitsWithOptionsNotImplementedErrorRaised(self): 

88 """ 

89 Test that subclasses have their own version 

90 of this implemented or an error is raised. 

91 """ 

92 class FakeStampsBase(stamps.StampsBase): 

93 def __init__(self): 

94 return 

95 

96 with self.assertRaises(NotImplementedError): 

97 FakeStampsBase.readFitsWithOptions('noFile', {}) 

98 

99 def testReadFitsWithOptionsMetadataError(self): 

100 """Test that error is raised when STAMPCLS returns None 

101 """ 

102 with tempfile.NamedTemporaryFile() as f: 

103 ss = make_stamps() 

104 emptyMetadata = PropertyList() 

105 stamps.writeFits( 

106 f.name, [ss[0]], emptyMetadata, None, True, True 

107 ) 

108 with self.assertRaises(RuntimeError): 

109 stamps.StampsBase.readFits(f.name) 

110 

111 def testReadFitsReturnsNewClass(self): 

112 """Test that readFits will return subclass 

113 """ 

114 class FakeStampsBase(stamps.StampsBase): 

115 def __init__(self): 

116 self._metadata = {} 

117 return 

118 

119 @classmethod 

120 def readFitsWithOptions(cls, filename, options): 

121 return cls() 

122 

123 def _refresh_metadata(self): 

124 self._metadata = {} 

125 

126 fakeStamps = FakeStampsBase.readFitsWithOptions('noFile', {}) 

127 self.assertEqual(type(fakeStamps), FakeStampsBase) 

128 

129 

130class StampsTestCase(lsst.utils.tests.TestCase): 

131 """Test Stamps. 

132 """ 

133 def testAppend(self): 

134 """Test ability to append to a Stamps object 

135 """ 

136 ss = make_stamps() 

137 s = ss[-1] 

138 ss.append(s) 

139 self.roundtrip(ss) 

140 # check if appending something other than a Stamp raises 

141 with self.assertRaises(ValueError): 

142 ss.append('hello world') 

143 

144 def testExtend(self): 

145 ss = make_stamps() 

146 ss2 = make_stamps() 

147 ss.extend([s for s in ss2]) 

148 # check if extending with something other than a Stamps 

149 # object raises 

150 with self.assertRaises(ValueError): 

151 ss.extend(['hello', 'world']) 

152 

153 def testIO(self): 

154 """Test the class' write and readFits methods. 

155 """ 

156 self.roundtrip(make_stamps()) 

157 

158 def testIOone(self): 

159 """Test the class' write and readFits methods for the special case of 

160 one stamp. 

161 """ 

162 self.roundtrip(make_stamps(1)) 

163 

164 def testIOsub(self): 

165 """Test the class' write and readFits when passing on a bounding box. 

166 """ 

167 bbox = geom.Box2I(geom.Point2I(3, 9), geom.Extent2I(11, 7)) 

168 ss = make_stamps() 

169 with tempfile.NamedTemporaryFile() as f: 

170 ss.writeFits(f.name) 

171 options = {'bbox': bbox} 

172 subStamps = stamps.Stamps.readFitsWithOptions(f.name, options) 

173 for s1, s2 in zip(ss, subStamps): 

174 self.assertEqual(bbox.getDimensions(), s2.stamp_im.getDimensions()) 

175 self.assertMaskedImagesAlmostEqual(s1.stamp_im[bbox], s2.stamp_im) 

176 

177 def testIOarchive(self): 

178 """Test the class' write and readFits when Stamps contain Persistables. 

179 """ 

180 self.roundtripWithArchive(make_stamps(use_archive=True)) 

181 

182 def testMetadata(self): 

183 """Test that metadata is correctly written and read. 

184 """ 

185 stamps = make_stamps() 

186 for stamp in stamps: 

187 self.assertTrue(stamp.metadata is not None) 

188 self.assertIn('RA_DEG', stamp.metadata) 

189 self.assertIn('DEC_DEG', stamp.metadata) 

190 

191 def roundtrip(self, ss): 

192 """Round trip a Stamps object to disk and check values 

193 """ 

194 with tempfile.NamedTemporaryFile() as f: 

195 ss.writeFits(f.name) 

196 options = PropertyList() 

197 ss2 = stamps.Stamps.readFitsWithOptions(f.name, options) 

198 self.assertEqual(len(ss), len(ss2)) 

199 for s1, s2 in zip(ss, ss2): 

200 self.assertMaskedImagesAlmostEqual(s1.stamp_im, s2.stamp_im) 

201 self.assertAlmostEqual(s1.position.getRa().asDegrees(), 

202 s2.position.getRa().asDegrees()) 

203 self.assertAlmostEqual(s1.position.getDec().asDegrees(), 

204 s2.position.getDec().asDegrees()) 

205 

206 for k, v in s1.metadata.items(): 

207 self.assertIn(k, s2.metadata) 

208 self.assertAlmostEqual(v, s2.metadata[k]) 

209 

210 def roundtripWithArchive(self, ss): 

211 """Round trip a Stamps object, including Archive elements, and check values 

212 """ 

213 transformTest = TransformTestClass() 

214 with tempfile.NamedTemporaryFile() as f: 

215 ss.writeFits(f.name) 

216 options = PropertyList() 

217 ss2 = stamps.Stamps.readFitsWithOptions(f.name, options) 

218 self.assertEqual(len(ss), len(ss2)) 

219 for s1, s2 in zip(ss, ss2): 

220 self.assertMaskedImagesAlmostEqual(s1.stamp_im, s2.stamp_im) 

221 self.assertAlmostEqual(s1.position.getRa().asDegrees(), 

222 s2.position.getRa().asDegrees()) 

223 self.assertAlmostEqual(s1.position.getDec().asDegrees(), 

224 s2.position.getDec().asDegrees()) 

225 transformTest.assertTransformsEqual(s1.archive_element, s2.archive_element) 

226 

227 

228class MemoryTester(lsst.utils.tests.MemoryTestCase): 

229 pass 

230 

231 

232def setup_module(module): 

233 lsst.utils.tests.init() 

234 

235 

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

237 lsst.utils.tests.init() 

238 unittest.main()