Coverage for tests / test_1079.py: 18%

137 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-23 01:26 -0700

1# This file is part of afw. 

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# test1079 

23# @brief Test that the wcs of sub-images are written and read from disk correctly 

24# $Id$ 

25# @author Fergal Mullally 

26 

27import os.path 

28import unittest 

29import numbers 

30 

31import lsst.utils 

32import lsst.geom 

33import lsst.afw.image as afwImage 

34from lsst.afw.fits import readMetadata 

35import lsst.utils.tests 

36import lsst.pex.exceptions 

37import lsst.afw.display as afwDisplay 

38 

39try: 

40 type(display) 

41except NameError: 

42 display = False 

43 

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

45 

46 

47class SavingSubImagesTest(unittest.TestCase): 

48 """Tests for changes made for ticket #1079. 

49 

50 In the LSST wcs transformations are done in terms of pixel position, which 

51 is measured from the lower left hand corner of the parent image from which 

52 this sub-image is drawn. However, when saving a sub-image to disk, the fits 

53 standards has no concept of parent- and sub- images, and specifies that the 

54 wcs is measured relative to the pixel index (i.e. the lower left hand corner 

55 of the sub-image). This test makes sure we're saving and reading wcs headers 

56 from sub-images correctly. 

57 """ 

58 

59 def setUp(self): 

60 self.parentFile = os.path.join(TESTDIR, "data", "parent.fits") 

61 

62 self.parent = afwImage.ExposureF(self.parentFile) 

63 self.llcParent = self.parent.getMaskedImage().getXY0() 

64 self.oParent = self.parent.getWcs().getPixelOrigin() 

65 

66 # A list of pixel positions to test 

67 self.testPositions = [] 

68 self.testPositions.append(lsst.geom.Point2D(128, 128)) 

69 self.testPositions.append(lsst.geom.Point2D(0, 0)) 

70 self.testPositions.append(lsst.geom.Point2D(20, 30)) 

71 self.testPositions.append(lsst.geom.Point2D(60, 50)) 

72 self.testPositions.append(lsst.geom.Point2D(80, 80)) 

73 self.testPositions.append(lsst.geom.Point2D(255, 255)) 

74 

75 self.parent.getMaskedImage().set(0) 

76 for p in self.testPositions: 

77 self.parent.image[lsst.geom.Point2I(p), afwImage.LOCAL] = 10 + p[0] 

78 

79 def tearDown(self): 

80 del self.parent 

81 del self.oParent 

82 del self.testPositions 

83 

84 def testInvarianceOfCrpix1(self): 

85 """Test that crpix is the same for parent and sub-image. 

86 

87 Also tests that llc of sub-image saved correctly. 

88 """ 

89 llc = lsst.geom.Point2I(20, 30) 

90 bbox = lsst.geom.Box2I(llc, lsst.geom.Extent2I(60, 50)) 

91 subImg = afwImage.ExposureF(self.parent, bbox, afwImage.LOCAL) 

92 

93 subImgLlc = subImg.getMaskedImage().getXY0() 

94 oSubImage = subImg.getWcs().getPixelOrigin() 

95 

96 # Useful for debugging 

97 if False: 

98 print(self.parent.getMaskedImage().getXY0()) 

99 print(subImg.getMaskedImage().getXY0()) 

100 print(self.oParent, oSubImage) 

101 

102 for i in range(2): 

103 self.assertEqual(llc[i], subImgLlc[i], "Corner of sub-image not correct") 

104 self.assertAlmostEqual( 

105 self.oParent[i], oSubImage[i], 6, "Crpix of sub-image not correct") 

106 

107 def testInvarianceOfCrpix2(self): 

108 """For sub-images loaded from disk, test that crpix is the same for parent and sub-image. 

109 

110 Also tests that llc of sub-image saved correctly. 

111 """ 

112 # Load sub-image directly off of disk 

113 llc = lsst.geom.Point2I(20, 30) 

114 bbox = lsst.geom.Box2I(llc, lsst.geom.Extent2I(60, 50)) 

115 subImg = afwImage.ExposureF(self.parentFile, bbox, afwImage.LOCAL) 

116 oSubImage = subImg.getWcs().getPixelOrigin() 

117 subImgLlc = subImg.getMaskedImage().getXY0() 

118 

119 # Useful for debugging 

120 if False: 

121 print(self.parent.getMaskedImage().getXY0()) 

122 print(subImg.getMaskedImage().getXY0()) 

123 print(self.oParent, oSubImage) 

124 

125 for i in range(2): 

126 self.assertEqual(llc[i], subImgLlc[i], "Corner of sub-image not correct") 

127 self.assertAlmostEqual( 

128 self.oParent[i], oSubImage[i], 6, "Crpix of sub-image not correct") 

129 

130 def testInvarianceOfPixelToSky(self): 

131 for deep in (True, False): 

132 llc = lsst.geom.Point2I(20, 30) 

133 bbox = lsst.geom.Box2I(llc, lsst.geom.Extent2I(60, 50)) 

134 subImg = afwImage.ExposureF(self.parent, bbox, afwImage.LOCAL, deep) 

135 

136 xy0 = subImg.getMaskedImage().getXY0() 

137 

138 if display: 

139 afwDisplay.Display(frame=0).mtv(self.parent, title=self._testMethodName + ": parent") 

140 afwDisplay.Display(frame=1).mtv(subImg, title=self._testMethodName + ": subImg") 

141 

142 for p in self.testPositions: 

143 subP = p - lsst.geom.Extent2D(llc[0], llc[1]) # pixel in subImg 

144 

145 if subP[0] < 0 or subP[0] >= bbox.getWidth() or subP[1] < 0 or subP[1] >= bbox.getHeight(): 

146 continue 

147 

148 adParent = self.parent.getWcs().pixelToSky(p) 

149 adSub = subImg.getWcs().pixelToSky(subP + lsst.geom.Extent2D(xy0[0], xy0[1])) 

150 # 

151 # Check that we're talking about the same pixel 

152 # 

153 self.assertEqual(self.parent.maskedImage[lsst.geom.Point2I(p), afwImage.LOCAL], 

154 subImg.maskedImage[lsst.geom.Point2I(subP), afwImage.LOCAL]) 

155 

156 self.assertEqual(adParent[0], adSub[0], f"RAs are equal; deep = {deep}") 

157 self.assertEqual(adParent[1], adSub[1], f"DECs are equal; deep = {deep}") 

158 

159 def testSubSubImage(self): 

160 """Check that a sub-image of a sub-image is equivalent to a sub image. 

161 

162 I.e. that the parent is an invarient. 

163 """ 

164 

165 llc1 = lsst.geom.Point2I(20, 30) 

166 bbox = lsst.geom.Box2I(llc1, lsst.geom.Extent2I(60, 50)) 

167 subImg = afwImage.ExposureF(self.parentFile, bbox, afwImage.LOCAL) 

168 

169 llc2 = lsst.geom.Point2I(22, 23) 

170 

171 # This subsub image should fail. Although it's big enough to fit in the parent image 

172 # it's too small for the sub-image 

173 bbox = lsst.geom.Box2I(llc2, lsst.geom.Extent2I(100, 110)) 

174 self.assertRaises(lsst.pex.exceptions.Exception, 

175 afwImage.ExposureF, subImg, bbox, afwImage.LOCAL) 

176 

177 bbox = lsst.geom.Box2I(llc2, lsst.geom.Extent2I(10, 11)) 

178 subSubImg = afwImage.ExposureF(subImg, bbox, afwImage.LOCAL) 

179 

180 sub0 = subImg.getMaskedImage().getXY0() 

181 subsub0 = subSubImg.getMaskedImage().getXY0() 

182 

183 if False: 

184 print(sub0) 

185 print(subsub0) 

186 

187 for i in range(2): 

188 self.assertEqual(llc1[i], sub0[i], "XY0 don't match (1)") 

189 self.assertEqual(llc1[i] + llc2[i], subsub0[i], "XY0 don't match (2)") 

190 

191 subCrpix = subImg.getWcs().getPixelOrigin() 

192 subsubCrpix = subSubImg.getWcs().getPixelOrigin() 

193 

194 for i in range(2): 

195 self.assertAlmostEqual( 

196 subCrpix[i], subsubCrpix[i], 6, "crpix don't match") 

197 

198 def testRoundTrip(self): 

199 """Test that saving and retrieving an image doesn't alter the metadata. 

200 """ 

201 llc = lsst.geom.Point2I(20, 30) 

202 bbox = lsst.geom.Box2I(llc, lsst.geom.Extent2I(60, 50)) 

203 for deep in (False, True): 

204 subImg = afwImage.ExposureF(self.parent, bbox, afwImage.LOCAL, deep) 

205 

206 with lsst.utils.tests.getTempFilePath(f"_{deep}.fits") as outFile: 

207 subImg.writeFits(outFile) 

208 newImg = afwImage.ExposureF(outFile) 

209 

210 subXY0 = subImg.getMaskedImage().getXY0() 

211 newXY0 = newImg.getMaskedImage().getXY0() 

212 

213 self.parent.getWcs().getPixelOrigin() 

214 subCrpix = subImg.getWcs().getPixelOrigin() 

215 newCrpix = newImg.getWcs().getPixelOrigin() 

216 

217 for i in range(2): 

218 self.assertEqual( 

219 subXY0[i], newXY0[i], f"Origin has changed; deep = {deep}") 

220 self.assertAlmostEqual( 

221 subCrpix[i], newCrpix[i], 6, f"crpix has changed; deep = {deep}") 

222 

223 def testFitsHeader(self): 

224 """Test that XY0 and crpix are written to the header as expected. 

225 """ 

226 # getPixelOrigin() returns origin in lsst coordinates, so need to add 1 to 

227 # compare to values stored in fits headers 

228 parentCrpix = self.parent.getWcs().getPixelOrigin() 

229 

230 # Make a sub-image 

231 x0, y0 = 20, 30 

232 llc = lsst.geom.Point2I(x0, y0) 

233 bbox = lsst.geom.Box2I(llc, lsst.geom.Extent2I(60, 50)) 

234 deep = False 

235 subImg = afwImage.ExposureF(self.parent, bbox, afwImage.LOCAL, deep) 

236 

237 with lsst.utils.tests.getTempFilePath(".fits") as outFile: 

238 subImg.writeFits(outFile) 

239 hdr = readMetadata(outFile) 

240 

241 def checkLtvHeader(hdr, name, value): 

242 # Per DM-4133, LTVn headers are required to be floating point 

243 self.assertTrue(hdr.exists(name), name + " not saved to FITS header") 

244 self.assertIsInstance( 

245 hdr.getScalar(name), numbers.Real, name + " is not numeric") 

246 self.assertNotIsInstance( 

247 hdr.getScalar(name), numbers.Integral, name + " is an int") 

248 self.assertEqual(hdr.getScalar(name), value, name + " has wrong value") 

249 

250 checkLtvHeader(hdr, "LTV1", -1*x0) 

251 checkLtvHeader(hdr, "LTV2", -1*y0) 

252 

253 self.assertTrue(hdr.exists("CRPIX1"), "CRPIX1 not saved to fits header") 

254 self.assertTrue(hdr.exists("CRPIX2"), "CRPIX2 not saved to fits header") 

255 

256 fitsCrpix = [hdr.getScalar("CRPIX1"), hdr.getScalar("CRPIX2")] 

257 self.assertAlmostEqual( 

258 fitsCrpix[0] - hdr.getScalar("LTV1"), parentCrpix[0] + 1, 6, "CRPIX1 saved wrong") 

259 self.assertAlmostEqual( 

260 fitsCrpix[1] - hdr.getScalar("LTV2"), parentCrpix[1] + 1, 6, "CRPIX2 saved wrong") 

261 

262 

263class TestMemory(lsst.utils.tests.MemoryTestCase): 

264 pass 

265 

266 

267def setup_module(module): 

268 lsst.utils.tests.init() 

269 

270 

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

272 lsst.utils.tests.init() 

273 unittest.main()