Coverage for tests / test_stamps.py: 23%

123 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-07 08:25 +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 

25 

26from lsst.meas.algorithms import stamps 

27from lsst.afw import image as afwImage 

28from lsst.afw.geom.testUtils import TransformTestBaseClass 

29from lsst.daf.base import PropertyList 

30import lsst.geom as geom 

31import lsst.afw.geom.transformFactory as tF 

32import lsst.utils.tests 

33 

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

35 

36 

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

38 stampSize = 25 

39 # create dummy stamp stamps 

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

41 for _ in range(n_stamps)] 

42 for stampIm in stampImages: 

43 stampImArray = stampIm.image.array 

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

45 stampMaskArray = stampIm.mask.array 

46 stampMaskArray += 10 

47 stampVarArray = stampIm.variance.array 

48 stampVarArray += 1000. 

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

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

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

52 for _ in range(n_stamps)] 

53 

54 metadata = PropertyList() 

55 metadata['RA_DEG'] = ras 

56 metadata['DEC_DEG'] = decs 

57 

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

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

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

61 archive_element=ae, 

62 metadata=metadata) 

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

64 

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

66 

67 

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

69 """Test StampsBase. 

70 """ 

71 def testReadFitsWithOptionsNotImplementedErrorRaised(self): 

72 """ 

73 Test that subclasses have their own version 

74 of this implemented or an error is raised. 

75 """ 

76 class FakeStampsBase(stamps.StampsBase): 

77 def __init__(self): 

78 return 

79 

80 with self.assertRaises(NotImplementedError): 

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

82 

83 def testReadFitsWithOptionsMetadataError(self): 

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

85 """ 

86 with tempfile.NamedTemporaryFile() as f: 

87 ss = make_stamps() 

88 emptyMetadata = PropertyList() 

89 stamps.writeFits( 

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

91 ) 

92 with self.assertRaises(RuntimeError): 

93 stamps.StampsBase.readFits(f.name) 

94 

95 def testReadFitsReturnsNewClass(self): 

96 """Test that readFits will return subclass 

97 """ 

98 class FakeStampsBase(stamps.StampsBase): 

99 def __init__(self): 

100 self._metadata = {} 

101 return 

102 

103 @classmethod 

104 def readFitsWithOptions(cls, filename, options): 

105 return cls() 

106 

107 def _refresh_metadata(self): 

108 self._metadata = {} 

109 

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

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

112 

113 

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

115 """Test Stamps. 

116 """ 

117 def testAppend(self): 

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

119 """ 

120 ss = make_stamps() 

121 s = ss[-1] 

122 ss.append(s) 

123 self.roundtrip(ss) 

124 # check if appending something other than a Stamp raises 

125 with self.assertRaises(ValueError): 

126 ss.append('hello world') 

127 

128 def testExtend(self): 

129 ss = make_stamps() 

130 ss2 = make_stamps() 

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

132 # check if extending with something other than a Stamps 

133 # object raises 

134 with self.assertRaises(ValueError): 

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

136 

137 def testIO(self): 

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

139 """ 

140 self.roundtrip(make_stamps()) 

141 

142 def testIOone(self): 

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

144 one stamp. 

145 """ 

146 self.roundtrip(make_stamps(1)) 

147 

148 def testIOsub(self): 

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

150 """ 

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

152 ss = make_stamps() 

153 with tempfile.NamedTemporaryFile() as f: 

154 ss.writeFits(f.name) 

155 options = {'bbox': bbox} 

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

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

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

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

160 

161 def testIOarchive(self): 

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

163 """ 

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

165 

166 def testMetadata(self): 

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

168 """ 

169 stamps = make_stamps() 

170 for stamp in stamps: 

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

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

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

174 

175 def roundtrip(self, ss): 

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

177 """ 

178 with tempfile.NamedTemporaryFile() as f: 

179 ss.writeFits(f.name) 

180 options = PropertyList() 

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

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

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

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

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

186 s2.position.getRa().asDegrees()) 

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

188 s2.position.getDec().asDegrees()) 

189 

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

191 self.assertIn(k, s2.metadata) 

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

193 

194 def roundtripWithArchive(self, ss): 

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

196 """ 

197 transformTest = TransformTestBaseClass() 

198 with tempfile.NamedTemporaryFile() as f: 

199 ss.writeFits(f.name) 

200 options = PropertyList() 

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

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

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

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

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

206 s2.position.getRa().asDegrees()) 

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

208 s2.position.getDec().asDegrees()) 

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

210 

211 

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

213 pass 

214 

215 

216def setup_module(module): 

217 lsst.utils.tests.init() 

218 

219 

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

221 lsst.utils.tests.init() 

222 unittest.main()