Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of ip_isr. 

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 itertools 

24import tempfile 

25 

26import numpy as np 

27 

28import lsst.geom 

29import lsst.utils.tests 

30import lsst.afw.image 

31import lsst.afw.table 

32import lsst.afw.cameraGeom as cameraGeom 

33 

34from lsst.pipe.base import Struct 

35from lsst.ip.isr import IsrTask, CrosstalkCalib, CrosstalkTask, NullCrosstalkTask 

36 

37try: 

38 display 

39except NameError: 

40 display = False 

41else: 

42 import lsst.afw.display as afwDisplay 

43 afwDisplay.setDefaultMaskTransparency(75) 

44 

45 

46outputName = None # specify a name (as a string) to save the output crosstalk coeffs. 

47 

48 

49class CrosstalkTestCase(lsst.utils.tests.TestCase): 

50 def setUp(self): 

51 width, height = 250, 500 

52 self.numAmps = 4 

53 numPixelsPerAmp = 1000 

54 # crosstalk[i][j] is the fraction of the j-th amp present on the i-th amp. 

55 self.crosstalk = [[0.0, 1e-4, 2e-4, 3e-4], 

56 [3e-4, 0.0, 2e-4, 1e-4], 

57 [4e-4, 5e-4, 0.0, 6e-4], 

58 [7e-4, 8e-4, 9e-4, 0.0]] 

59 self.value = 12345 

60 self.crosstalkStr = "XTLK" 

61 

62 # A bit of noise is important, because otherwise the pixel distributions are razor-thin 

63 # and then rejection doesn't work 

64 rng = np.random.RandomState(12345) 

65 self.noise = rng.normal(0.0, 0.1, (2*height, 2*width)) 

66 

67 # Create amp images 

68 withoutCrosstalk = [lsst.afw.image.ImageF(width, height) for _ in range(self.numAmps)] 

69 for image in withoutCrosstalk: 

70 image.set(0) 

71 xx = rng.randint(0, width, numPixelsPerAmp) 

72 yy = rng.randint(0, height, numPixelsPerAmp) 

73 image.getArray()[yy, xx] = self.value 

74 

75 # Add in crosstalk 

76 withCrosstalk = [image.Factory(image, True) for image in withoutCrosstalk] 

77 for ii, iImage in enumerate(withCrosstalk): 

78 for jj, jImage in enumerate(withoutCrosstalk): 

79 value = self.crosstalk[ii][jj] 

80 iImage.scaledPlus(value, jImage) 

81 

82 # Put amp images together 

83 def construct(imageList): 

84 image = lsst.afw.image.ImageF(2*width, 2*height) 

85 image.getArray()[:height, :width] = imageList[0].getArray() 

86 image.getArray()[:height, width:] = imageList[1].getArray()[:, ::-1] # flip in x 

87 image.getArray()[height:, :width] = imageList[2].getArray()[::-1, :] # flip in y 

88 image.getArray()[height:, width:] = imageList[3].getArray()[::-1, ::-1] # flip in x and y 

89 image.getArray()[:] += self.noise 

90 return image 

91 

92 # Construct detector 

93 detName = 'detector' 

94 detId = 1 

95 detSerial = 'serial' 

96 orientation = cameraGeom.Orientation() 

97 pixelSize = lsst.geom.Extent2D(1, 1) 

98 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

99 lsst.geom.Extent2I(2*width, 2*height)) 

100 crosstalk = np.array(self.crosstalk, dtype=np.float32) 

101 

102 camBuilder = cameraGeom.Camera.Builder("fakeCam") 

103 detBuilder = camBuilder.add(detName, detId) 

104 detBuilder.setSerial(detSerial) 

105 detBuilder.setBBox(bbox) 

106 detBuilder.setOrientation(orientation) 

107 detBuilder.setPixelSize(pixelSize) 

108 detBuilder.setCrosstalk(crosstalk) 

109 

110 # Create amp info 

111 for ii, (xx, yy, corner) in enumerate([(0, 0, lsst.afw.cameraGeom.ReadoutCorner.LL), 

112 (width, 0, lsst.afw.cameraGeom.ReadoutCorner.LR), 

113 (0, height, lsst.afw.cameraGeom.ReadoutCorner.UL), 

114 (width, height, lsst.afw.cameraGeom.ReadoutCorner.UR)]): 

115 

116 amp = cameraGeom.Amplifier.Builder() 

117 amp.setName("amp %d" % ii) 

118 amp.setBBox(lsst.geom.Box2I(lsst.geom.Point2I(xx, yy), 

119 lsst.geom.Extent2I(width, height))) 

120 amp.setRawDataBBox(lsst.geom.Box2I(lsst.geom.Point2I(xx, yy), 

121 lsst.geom.Extent2I(width, height))) 

122 amp.setReadoutCorner(corner) 

123 detBuilder.append(amp) 

124 

125 cam = camBuilder.finish() 

126 ccd = cam.get('detector') 

127 

128 self.exposure = lsst.afw.image.makeExposure(lsst.afw.image.makeMaskedImage(construct(withCrosstalk))) 

129 self.exposure.setDetector(ccd) 

130 

131 self.corrected = construct(withoutCrosstalk) 

132 

133 if display: 

134 disp = lsst.afw.display.Display(frame=1) 

135 disp.mtv(self.exposure, title="exposure") 

136 disp = lsst.afw.display.Display(frame=0) 

137 disp.mtv(self.corrected, title="corrected exposure") 

138 

139 def tearDown(self): 

140 del self.exposure 

141 del self.corrected 

142 

143 def checkCoefficients(self, coeff, coeffErr, coeffNum): 

144 """Check that coefficients are as expected 

145 

146 Parameters 

147 ---------- 

148 coeff : `numpy.ndarray` 

149 Crosstalk coefficients. 

150 coeffErr : `numpy.ndarray` 

151 Crosstalk coefficient errors. 

152 coeffNum : `numpy.ndarray` 

153 Number of pixels to produce each coefficient. 

154 """ 

155 for matrix in (coeff, coeffErr, coeffNum): 

156 self.assertEqual(matrix.shape, (self.numAmps, self.numAmps)) 

157 self.assertFloatsAlmostEqual(coeff, np.array(self.crosstalk), atol=1.0e-6) 

158 

159 for ii in range(self.numAmps): 

160 self.assertEqual(coeff[ii, ii], 0.0) 

161 self.assertTrue(np.isnan(coeffErr[ii, ii])) 

162 self.assertEqual(coeffNum[ii, ii], 1) 

163 

164 self.assertTrue(np.all(coeffErr[ii, jj] > 0 for ii, jj in 

165 itertools.product(range(self.numAmps), range(self.numAmps)) if ii != jj)) 

166 self.assertTrue(np.all(coeffNum[ii, jj] > 0 for ii, jj in 

167 itertools.product(range(self.numAmps), range(self.numAmps)) if ii != jj)) 

168 

169 def checkSubtracted(self, exposure): 

170 """Check that the subtracted image is as expected 

171 

172 Parameters 

173 ---------- 

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

175 Crosstalk-subtracted exposure. 

176 """ 

177 image = exposure.getMaskedImage().getImage() 

178 mask = exposure.getMaskedImage().getMask() 

179 self.assertFloatsAlmostEqual(image.getArray(), self.corrected.getArray(), atol=2.0e-2) 

180 self.assertIn(self.crosstalkStr, mask.getMaskPlaneDict()) 

181 self.assertGreater((mask.getArray() & mask.getPlaneBitMask(self.crosstalkStr) > 0).sum(), 0) 

182 

183 def testDirectAPI(self): 

184 """Test that individual function calls work""" 

185 calib = CrosstalkCalib() 

186 calib.coeffs = np.array(self.crosstalk).transpose() 

187 calib.subtractCrosstalk(self.exposure, crosstalkCoeffs=calib.coeffs, 

188 minPixelToMask=self.value - 1, 

189 crosstalkStr=self.crosstalkStr) 

190 self.checkSubtracted(self.exposure) 

191 

192 outPath = tempfile.mktemp() if outputName is None else "{}-isrCrosstalk".format(outputName) 

193 outPath += '.yaml' 

194 calib.writeText(outPath) 

195 

196 def testTaskAPI(self): 

197 """Test that the Tasks work 

198 

199 Checks both MeasureCrosstalkTask and the CrosstalkTask. 

200 """ 

201 # make exposure available to NullIsrTask 

202 # without NullIsrTask's `self` hiding this test class's `self` 

203 exposure = self.exposure 

204 

205 class NullIsrTask(IsrTask): 

206 def runDataRef(self, dataRef): 

207 return Struct(exposure=exposure) 

208 

209 coeff = np.array(self.crosstalk).transpose() 

210 config = IsrTask.ConfigClass() 

211 config.crosstalk.minPixelToMask = self.value - 1 

212 config.crosstalk.crosstalkMaskPlane = self.crosstalkStr 

213 isr = IsrTask(config=config) 

214 calib = CrosstalkCalib().fromDetector(self.exposure.getDetector(), coeffVector=coeff) 

215 isr.crosstalk.run(self.exposure, crosstalk=calib) 

216 self.checkSubtracted(self.exposure) 

217 

218 def test_prepCrosstalk(self): 

219 """Test that prep crosstalk does not error when given a dataRef with no 

220 crosstalkSources to find. 

221 """ 

222 dataRef = Struct(dataId={'fake': 1}) 

223 task = CrosstalkTask() 

224 result = task.prepCrosstalk(dataRef) 

225 self.assertIsNone(result) 

226 

227 def test_nullCrosstalkTask(self): 

228 """Test that the null crosstalk task does not create an error. 

229 """ 

230 exposure = self.exposure 

231 task = NullCrosstalkTask() 

232 result = task.run(exposure, crosstalkSources=None) 

233 self.assertIsNone(result) 

234 

235 

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

237 pass 

238 

239 

240def setup_module(module): 

241 lsst.utils.tests.init() 

242 

243 

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

245 import sys 

246 setup_module(sys.modules[__name__]) 

247 unittest.main()