Coverage for tests/test_overscanCorrection.py: 7%

322 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-05 10:32 +0000

1# 

2# LSST Data Management System 

3# Copyright 2008, 2009, 2010 LSST Corporation. 

4# 

5# This product includes software developed by the 

6# LSST Project (http://www.lsst.org/). 

7# 

8# This program is free software: you can redistribute it and/or modify 

9# it under the terms of the GNU General Public License as published by 

10# the Free Software Foundation, either version 3 of the License, or 

11# (at your option) any later version. 

12# 

13# This program is distributed in the hope that it will be useful, 

14# but WITHOUT ANY WARRANTY; without even the implied warranty of 

15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

16# GNU General Public License for more details. 

17# 

18# You should have received a copy of the LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <http://www.lsstcorp.org/LegalNotices/>. 

21# 

22 

23import unittest 

24import numpy as np 

25 

26import lsst.utils.tests 

27import lsst.geom 

28import lsst.afw.image as afwImage 

29import lsst.afw.cameraGeom as cameraGeom 

30import lsst.ip.isr as ipIsr 

31import lsst.pipe.base as pipeBase 

32 

33 

34def computeImageMedianAndStd(image): 

35 """Function to calculate median and std of image data. 

36 

37 Parameters 

38 ---------- 

39 image : `lsst.afw.image.Image` 

40 Image to measure statistics on. 

41 

42 Returns 

43 ------- 

44 median : `float` 

45 Image median. 

46 std : `float` 

47 Image stddev. 

48 """ 

49 median = np.nanmedian(image.getArray()) 

50 std = np.nanstd(image.getArray()) 

51 

52 return (median, std) 

53 

54 

55class IsrTestCases(lsst.utils.tests.TestCase): 

56 

57 def updateConfigFromKwargs(self, config, **kwargs): 

58 """Common config from keywords. 

59 """ 

60 fitType = kwargs.get('fitType', None) 

61 if fitType: 

62 config.overscan.fitType = fitType 

63 

64 order = kwargs.get('order', None) 

65 if order: 

66 config.overscan.order = order 

67 

68 def makeExposure(self, addRamp=False, isTransposed=False): 

69 # Define the camera geometry we'll use. 

70 cameraBuilder = cameraGeom.Camera.Builder("Fake Camera") 

71 detectorBuilder = cameraBuilder.add("Fake amp", 0) 

72 

73 ampBuilder = cameraGeom.Amplifier.Builder() 

74 

75 dataBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

76 lsst.geom.Extent2I(10, 10)) 

77 

78 if isTransposed is True: 

79 fullBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

80 lsst.geom.Point2I(12, 12)) 

81 serialOverscanBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 10), 

82 lsst.geom.Point2I(9, 12)) 

83 parallelOverscanBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 0), 

84 lsst.geom.Point2I(12, 9)) 

85 else: 

86 fullBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

87 lsst.geom.Point2I(12, 12)) 

88 serialOverscanBBox = lsst.geom.Box2I(lsst.geom.Point2I(10, 0), 

89 lsst.geom.Point2I(12, 9)) 

90 parallelOverscanBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 10), 

91 lsst.geom.Point2I(9, 12)) 

92 

93 ampBuilder.setRawBBox(fullBBox) 

94 ampBuilder.setRawSerialOverscanBBox(serialOverscanBBox) 

95 ampBuilder.setRawParallelOverscanBBox(parallelOverscanBBox) 

96 ampBuilder.setRawDataBBox(dataBBox) 

97 

98 detectorBuilder.append(ampBuilder) 

99 camera = cameraBuilder.finish() 

100 detector = camera[0] 

101 

102 # Define image data. 

103 maskedImage = afwImage.MaskedImageF(fullBBox) 

104 maskedImage.set(2, 0x0, 1) 

105 

106 dataImage = afwImage.MaskedImageF(maskedImage, dataBBox) 

107 dataImage.set(10, 0x0, 1) 

108 

109 if addRamp: 

110 for column in range(dataBBox.getWidth()): 

111 maskedImage.image.array[:, column] += column 

112 

113 exposure = afwImage.ExposureF(maskedImage, None) 

114 exposure.setDetector(detector) 

115 return exposure 

116 

117 def checkOverscanCorrectionY(self, **kwargs): 

118 exposure = self.makeExposure(isTransposed=True) 

119 detector = exposure.getDetector() 

120 

121 # These subimages are needed below. 

122 overscan = exposure[detector.getAmplifiers()[0].getRawSerialOverscanBBox()] 

123 maskedImage = exposure[detector.getAmplifiers()[0].getRawBBox()] 

124 

125 config = ipIsr.IsrTask.ConfigClass() 

126 self.updateConfigFromKwargs(config, **kwargs) 

127 

128 if kwargs['fitType'] == "MEDIAN_PER_ROW": 

129 # Add a bad point to test outlier rejection. 

130 overscan.getImage().getArray()[0, 0] = 12345 

131 

132 # Shrink the sigma clipping limit to handle the fact that the 

133 # bad point is not be rejected at higher thresholds (2/0.74). 

134 config.overscan.numSigmaClip = 2.7 

135 

136 isrTask = ipIsr.IsrTask(config=config) 

137 isrTask.overscan.run(exposure, detector.getAmplifiers()[0], isTransposed=True) 

138 

139 height = maskedImage.getHeight() 

140 width = maskedImage.getWidth() 

141 for j in range(height): 

142 for i in range(width): 

143 if j == 10 and i == 0 and kwargs['fitType'] == "MEDIAN_PER_ROW": 

144 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 12343) 

145 elif j >= 10 and i < 10: 

146 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0) 

147 elif i < 10: 

148 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 8) 

149 

150 def checkOverscanCorrectionX(self, **kwargs): 

151 exposure = self.makeExposure(isTransposed=False) 

152 detector = exposure.getDetector() 

153 

154 # These subimages are needed below. 

155 maskedImage = exposure[detector.getAmplifiers()[0].getRawBBox()] 

156 

157 config = ipIsr.IsrTask.ConfigClass() 

158 self.updateConfigFromKwargs(config, **kwargs) 

159 

160 isrTask = ipIsr.IsrTask(config=config) 

161 isrTask.overscan.run(exposure, detector.getAmplifiers()[0], isTransposed=False) 

162 

163 height = maskedImage.getHeight() 

164 width = maskedImage.getWidth() 

165 for j in range(height): 

166 for i in range(width): 

167 if i >= 10 and j < 10: 

168 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0) 

169 elif j < 10: 

170 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 8) 

171 

172 def checkOverscanCorrectionSineWave(self, **kwargs): 

173 """vertical sine wave along long direction""" 

174 # Define the camera geometry we'll use. 

175 cameraBuilder = cameraGeom.Camera.Builder("Fake Camera") 

176 detectorBuilder = cameraBuilder.add("Fake amp", 0) 

177 

178 ampBuilder = cameraGeom.Amplifier.Builder() 

179 

180 dataBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

181 lsst.geom.Extent2I(70, 500)) 

182 

183 fullBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

184 lsst.geom.Extent2I(100, 500)) 

185 

186 overscanBBox = lsst.geom.Box2I(lsst.geom.Point2I(70, 0), 

187 lsst.geom.Extent2I(30, 500)) 

188 

189 ampBuilder.setRawBBox(fullBBox) 

190 ampBuilder.setRawSerialOverscanBBox(overscanBBox) 

191 ampBuilder.setRawDataBBox(dataBBox) 

192 

193 detectorBuilder.append(ampBuilder) 

194 camera = cameraBuilder.finish() 

195 detector = camera[0] 

196 

197 # Define image data. 

198 maskedImage = afwImage.MaskedImageF(fullBBox) 

199 maskedImage.set(50, 0x0, 1) 

200 

201 overscan = afwImage.MaskedImageF(maskedImage, overscanBBox) 

202 overscan.set(0, 0x0, 1) 

203 

204 exposure = afwImage.ExposureF(maskedImage, None) 

205 exposure.setDetector(detector) 

206 

207 # vertical sine wave along long direction 

208 x = np.linspace(0, 2*3.14159, 500) 

209 a, w = 15, 5*3.14159 

210 sineWave = 20 + a*np.sin(w*x) 

211 sineWave = sineWave.astype(int) 

212 

213 fullImage = np.repeat(sineWave, 100).reshape((500, 100)) 

214 maskedImage.image.array += fullImage 

215 

216 config = ipIsr.IsrTask.ConfigClass() 

217 self.updateConfigFromKwargs(config, **kwargs) 

218 

219 isrTask = ipIsr.IsrTask(config=config) 

220 isrTask.overscan.run(exposure, detector.getAmplifiers()[0]) 

221 

222 height = maskedImage.getHeight() 

223 width = maskedImage.getWidth() 

224 

225 for j in range(height): 

226 for i in range(width): 

227 if i >= 70: 

228 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0.0) 

229 else: 

230 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 50.0) 

231 

232 def test_MedianPerRowOverscanCorrection(self): 

233 self.checkOverscanCorrectionY(fitType="MEDIAN_PER_ROW") 

234 self.checkOverscanCorrectionX(fitType="MEDIAN_PER_ROW") 

235 self.checkOverscanCorrectionSineWave(fitType="MEDIAN_PER_ROW") 

236 

237 def test_MedianOverscanCorrection(self): 

238 self.checkOverscanCorrectionY(fitType="MEDIAN") 

239 self.checkOverscanCorrectionX(fitType="MEDIAN") 

240 

241 def checkPolyOverscanCorrectionX(self, **kwargs): 

242 exposure = self.makeExposure(isTransposed=False) 

243 detector = exposure.getDetector() 

244 

245 # These subimages are needed below. 

246 overscan = exposure[detector.getAmplifiers()[0].getRawSerialOverscanBBox()] 

247 maskedImage = exposure[detector.getAmplifiers()[0].getRawBBox()] 

248 

249 bbox = detector.getAmplifiers()[0].getRawSerialOverscanBBox() 

250 overscan.getMaskedImage().set(2, 0x0, 1) 

251 for i in range(bbox.getDimensions()[1]): 

252 for j, off in enumerate([-0.5, 0.0, 0.5]): 

253 overscan.image[j, i, afwImage.LOCAL] = 2+i+off 

254 

255 config = ipIsr.IsrTask.ConfigClass() 

256 self.updateConfigFromKwargs(config, **kwargs) 

257 

258 isrTask = ipIsr.IsrTask(config=config) 

259 isrTask.overscan.run(exposure, detector.getAmplifiers()[0], isTransposed=False) 

260 

261 height = maskedImage.getHeight() 

262 width = maskedImage.getWidth() 

263 for j in range(height): 

264 for i in range(width): 

265 if j < 10: 

266 if i == 10: 

267 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], -0.5) 

268 elif i == 11: 

269 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0) 

270 elif i == 12: 

271 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0.5) 

272 else: 

273 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 10 - 2 - j) 

274 

275 def checkPolyOverscanCorrectionY(self, **kwargs): 

276 exposure = self.makeExposure(isTransposed=True) 

277 detector = exposure.getDetector() 

278 

279 # These subimages are needed below. 

280 overscan = exposure[detector.getAmplifiers()[0].getRawSerialOverscanBBox()] 

281 maskedImage = exposure[detector.getAmplifiers()[0].getRawBBox()] 

282 

283 bbox = detector.getAmplifiers()[0].getRawSerialOverscanBBox() 

284 overscan.getMaskedImage().set(2, 0x0, 1) 

285 for i in range(bbox.getDimensions()[0]): 

286 for j, off in enumerate([-0.5, 0.0, 0.5]): 

287 overscan.image[i, j, afwImage.LOCAL] = 2+i+off 

288 # maskedImage.getMaskedImage().set(10, 0x0, 1) 

289 

290 config = ipIsr.IsrTask.ConfigClass() 

291 self.updateConfigFromKwargs(config, **kwargs) 

292 

293 isrTask = ipIsr.IsrTask(config=config) 

294 isrTask.overscan.run(exposure, detector.getAmplifiers()[0], isTransposed=True) 

295 

296 height = maskedImage.getHeight() 

297 width = maskedImage.getWidth() 

298 for j in range(height): 

299 for i in range(width): 

300 if i < 10: 

301 if j == 10: 

302 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], -0.5) 

303 elif j == 11: 

304 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0) 

305 elif j == 12: 

306 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 0.5) 

307 else: 

308 self.assertEqual(maskedImage.image[i, j, afwImage.LOCAL], 10 - 2 - i) 

309 

310 def test_PolyOverscanCorrection(self): 

311 for fitType in ("POLY", "CHEB", "LEG"): 

312 self.checkPolyOverscanCorrectionX(fitType=fitType, order=5) 

313 self.checkPolyOverscanCorrectionY(fitType=fitType, order=5) 

314 

315 def test_SplineOverscanCorrection(self): 

316 for fitType in ("NATURAL_SPLINE", "CUBIC_SPLINE", "AKIMA_SPLINE"): 

317 self.checkPolyOverscanCorrectionX(fitType=fitType, order=5) 

318 self.checkPolyOverscanCorrectionY(fitType=fitType, order=5) 

319 

320 def test_overscanCorrection(self): 

321 """Expect that this should reduce the image variance with a full fit. 

322 The default fitType of MEDIAN will reduce the median value. 

323 

324 This needs to operate on a RawMock() to have overscan data to use. 

325 

326 The output types may be different when fitType != MEDIAN. 

327 """ 

328 exposure = self.makeExposure(isTransposed=False) 

329 detector = exposure.getDetector() 

330 amp = detector.getAmplifiers()[0] 

331 

332 statBefore = computeImageMedianAndStd(exposure.image[amp.getRawDataBBox()]) 

333 

334 config = ipIsr.IsrTask.ConfigClass() 

335 isrTask = ipIsr.IsrTask(config=config) 

336 oscanResults = isrTask.overscan.run(exposure, amp) 

337 

338 self.assertIsInstance(oscanResults, pipeBase.Struct) 

339 self.assertIsInstance(oscanResults.imageFit, float) 

340 self.assertIsInstance(oscanResults.overscanFit, float) 

341 self.assertIsInstance(oscanResults.overscanImage, afwImage.ExposureF) 

342 

343 statAfter = computeImageMedianAndStd(exposure.image[amp.getRawDataBBox()]) 

344 self.assertLess(statAfter[0], statBefore[0]) 

345 

346 def test_parallelOverscanCorrection(self): 

347 """Expect that this should reduce the image variance with a full fit. 

348 The default fitType of MEDIAN will reduce the median value. 

349 

350 This needs to operate on a RawMock() to have overscan data to use. 

351 

352 This test checks that the outputs match, and that the serial 

353 overscan is the trivial value (2.0), and that the parallel 

354 overscan is the median of the ramp inserted (4.5) 

355 """ 

356 exposure = self.makeExposure(addRamp=True, isTransposed=False) 

357 detector = exposure.getDetector() 

358 amp = detector.getAmplifiers()[0] 

359 

360 statBefore = computeImageMedianAndStd(exposure.image[amp.getRawDataBBox()]) 

361 

362 for fitType in ('MEDIAN', 'MEDIAN_PER_ROW'): 

363 # This tests these two types to cover scalar and vector 

364 # calculations. 

365 exposureCopy = exposure.clone() 

366 config = ipIsr.IsrTask.ConfigClass() 

367 config.overscan.doParallelOverscan = True 

368 config.overscan.fitType = fitType 

369 isrTask = ipIsr.IsrTask(config=config) 

370 

371 oscanResults = isrTask.overscan.run(exposureCopy, amp) 

372 

373 self.assertIsInstance(oscanResults, pipeBase.Struct) 

374 if fitType == 'MEDIAN': 

375 self.assertIsInstance(oscanResults.imageFit, float) 

376 self.assertIsInstance(oscanResults.overscanFit, float) 

377 else: 

378 self.assertIsInstance(oscanResults.imageFit, np.ndarray) 

379 self.assertIsInstance(oscanResults.overscanFit, np.ndarray) 

380 self.assertIsInstance(oscanResults.overscanImage, afwImage.ExposureF) 

381 

382 statAfter = computeImageMedianAndStd(exposureCopy.image[amp.getRawDataBBox()]) 

383 self.assertLess(statAfter[0], statBefore[0]) 

384 

385 # Test the output value for the serial and parallel overscans 

386 self.assertAlmostEqual(oscanResults.overscanMean[0], 2.0, delta=0.001) 

387 self.assertAlmostEqual(oscanResults.overscanMean[1], 4.5, delta=0.001) 

388 if fitType != 'MEDIAN': 

389 # The ramp that has been inserted should be fully 

390 # removed by the overscan fit, removing all of the 

391 # signal. This isn't true of the constant fit, so do 

392 # not test that here. 

393 self.assertLess(statAfter[1], statBefore[1]) 

394 self.assertAlmostEqual(statAfter[1], 0.0, delta=0.001) 

395 

396 def test_bleedParallelOverscanCorrection(self): 

397 """Expect that this should reduce the image variance with a full fit. 

398 The default fitType of MEDIAN will reduce the median value. 

399 

400 This needs to operate on a RawMock() to have overscan data to use. 

401 

402 This test adds a large artificial bleed to the overscan region, 

403 which should be masked and patched with the median of the 

404 other pixels. 

405 """ 

406 exposure = self.makeExposure(addRamp=True, isTransposed=False) 

407 detector = exposure.getDetector() 

408 amp = detector.getAmplifiers()[0] 

409 

410 maskedImage = exposure.getMaskedImage() 

411 overscanBleedBox = lsst.geom.Box2I(lsst.geom.Point2I(4, 10), 

412 lsst.geom.Extent2I(2, 3)) 

413 overscanBleed = afwImage.MaskedImageF(maskedImage, overscanBleedBox) 

414 overscanBleed.set(110000, 0x0, 1) 

415 

416 statBefore = computeImageMedianAndStd(exposure.image[amp.getRawDataBBox()]) 

417 

418 for fitType in ('MEDIAN', 'MEDIAN_PER_ROW', 'POLY'): 

419 # We only test these three types as this should cover the 

420 # scalar calculations, the generic vector calculations, 

421 # and the specific C++ MEDIAN_PER_ROW case. 

422 exposureCopy = exposure.clone() 

423 config = ipIsr.IsrTask.ConfigClass() 

424 config.overscan.doParallelOverscan = True 

425 config.overscan.parallelOverscanMaskGrowSize = 1 

426 config.overscan.fitType = fitType 

427 isrTask = ipIsr.IsrTask(config=config) 

428 

429 # This next line is usually run as part of IsrTask: 

430 isrTask.overscan.maskParallelOverscan(exposureCopy, detector) 

431 oscanResults = isrTask.overscan.run(exposureCopy, amp) 

432 

433 self.assertIsInstance(oscanResults, pipeBase.Struct) 

434 if fitType == 'MEDIAN': 

435 self.assertIsInstance(oscanResults.imageFit, float) 

436 self.assertIsInstance(oscanResults.overscanFit, float) 

437 else: 

438 self.assertIsInstance(oscanResults.imageFit, np.ndarray) 

439 self.assertIsInstance(oscanResults.overscanFit, np.ndarray) 

440 self.assertIsInstance(oscanResults.overscanImage, afwImage.ExposureF) 

441 

442 statAfter = computeImageMedianAndStd(exposureCopy.image[amp.getRawDataBBox()]) 

443 self.assertLess(statAfter[0], statBefore[0]) 

444 

445 # Test the output value for the serial and parallel 

446 # overscans. 

447 self.assertAlmostEqual(oscanResults.overscanMean[0], 2.0, delta=0.001) 

448 self.assertAlmostEqual(oscanResults.overscanMean[1], 4.5, delta=0.001) 

449 self.assertAlmostEqual(oscanResults.residualMean[1], 0.0, delta=0.001) 

450 if fitType != 'MEDIAN': 

451 # Check the bleed isn't oversubtracted. This is the 

452 # average of the two mid-bleed pixels as the patching 

453 # uses the median correction value there, and there is 

454 # still a residual ramp in this region. The large 

455 # delta allows the POLY fit to pass, which has sub-ADU 

456 # differences. 

457 self.assertAlmostEqual(exposureCopy.image.array[5][0], 

458 0.5 * (exposureCopy.image.array[5][4] 

459 + exposureCopy.image.array[5][5]), delta=0.3) 

460 # These fits should also reduce the image stdev, as 

461 # they are modeling the ramp. 

462 self.assertLess(statAfter[1], statBefore[1]) 

463 

464 def test_bleedParallelOverscanCorrectionFailure(self): 

465 """Expect that this should reduce the image variance with a full fit. 

466 The default fitType of MEDIAN will reduce the median value. 

467 

468 This needs to operate on a RawMock() to have overscan data to use. 

469 

470 This adds a large artificial bleed to the overscan region, 

471 which should be masked and patched with the median of the 

472 other pixels. 

473 """ 

474 exposure = self.makeExposure(addRamp=True, isTransposed=False) 

475 detector = exposure.getDetector() 

476 amp = detector.getAmplifiers()[0] 

477 

478 maskedImage = exposure.getMaskedImage() 

479 overscanBleedBox = lsst.geom.Box2I(lsst.geom.Point2I(4, 10), 

480 lsst.geom.Extent2I(2, 3)) 

481 overscanBleed = afwImage.MaskedImageF(maskedImage, overscanBleedBox) 

482 overscanBleed.set(10000, 0x0, 1) # This level is below the mask threshold. 

483 

484 statBefore = computeImageMedianAndStd(exposure.image[amp.getRawDataBBox()]) 

485 

486 for fitType in ('MEDIAN', 'MEDIAN_PER_ROW'): 

487 # We only test these three types as this should cover the 

488 # scalar calculations, the generic vector calculations, 

489 # and the specific C++ MEDIAN_PER_ROW case. 

490 exposureCopy = exposure.clone() 

491 config = ipIsr.IsrTask.ConfigClass() 

492 config.overscan.doParallelOverscan = True 

493 config.overscan.parallelOverscanMaskGrowSize = 1 

494 # Ensure we don't mask anything 

495 config.overscan.maxDeviation = 100000 

496 config.overscan.fitType = fitType 

497 isrTask = ipIsr.IsrTask(config=config) 

498 

499 # isrTask.overscan.maskParallelOverscan(exposureCopy, detector) 

500 oscanResults = isrTask.overscan.run(exposureCopy, amp) 

501 

502 self.assertIsInstance(oscanResults, pipeBase.Struct) 

503 if fitType == 'MEDIAN': 

504 self.assertIsInstance(oscanResults.imageFit, float) 

505 self.assertIsInstance(oscanResults.overscanFit, float) 

506 else: 

507 self.assertIsInstance(oscanResults.imageFit, np.ndarray) 

508 self.assertIsInstance(oscanResults.overscanFit, np.ndarray) 

509 self.assertIsInstance(oscanResults.overscanImage, afwImage.ExposureF) 

510 

511 statAfter = computeImageMedianAndStd(exposureCopy.image[amp.getRawDataBBox()]) 

512 self.assertLess(statAfter[0], statBefore[0]) 

513 

514 # Test the output value for the serial and parallel 

515 # overscans. 

516 self.assertAlmostEqual(oscanResults.overscanMean[0], 2.0, delta=0.001) 

517 # These are the wrong values: 

518 if fitType == 'MEDIAN': 

519 # Check that the constant case is now biased, at 6.5 

520 # instead of 4.5: 

521 self.assertAlmostEqual(oscanResults.overscanMean[1], 6.5, delta=0.001) 

522 else: 

523 # This is not correcting the bleed, so it will be printed 

524 # onto the image, making the stdev after correction worse 

525 # than before. 

526 self.assertGreater(statAfter[1], statBefore[1]) 

527 

528 # Check that the median overscan value matches the 

529 # constant fit: 

530 self.assertAlmostEqual(oscanResults.overscanMedian[1], 6.5, delta=0.001) 

531 # Check that the mean isn't what we found before, and 

532 # is larger: 

533 self.assertNotEqual(oscanResults.overscanMean[1], 4.5) 

534 self.assertGreater(oscanResults.overscanMean[1], 4.5) 

535 self.assertGreater(exposureCopy.image.array[5][0], 

536 0.5 * (exposureCopy.image.array[5][4] 

537 + exposureCopy.image.array[5][5])) 

538 

539 def test_overscanCorrection_isNotInt(self): 

540 """Expect smaller median/smaller std after. 

541 Expect exception if overscan fit type isn't known. 

542 """ 

543 exposure = self.makeExposure(isTransposed=False) 

544 detector = exposure.getDetector() 

545 amp = detector.getAmplifiers()[0] 

546 

547 for fitType in ('MEAN', 'MEDIAN', 'MEDIAN_PER_ROW', 'MEANCLIP', 'POLY', 'CHEB', 

548 'NATURAL_SPLINE', 'CUBIC_SPLINE'): 

549 if fitType in ('NATURAL_SPLINE', 'CUBIC_SPLINE'): 

550 order = 3 

551 else: 

552 order = 1 

553 config = ipIsr.IsrTask.ConfigClass() 

554 config.overscan.order = order 

555 config.overscan.fitType = fitType 

556 isrTask = ipIsr.IsrTask(config=config) 

557 

558 response = isrTask.overscan.run(exposure, amp) 

559 

560 self.assertIsInstance(response, pipeBase.Struct, 

561 msg=f"overscanCorrection overscanIsNotInt Bad response: {fitType}") 

562 self.assertIsNotNone(response.imageFit, 

563 msg=f"overscanCorrection overscanIsNotInt Bad imageFit: {fitType}") 

564 self.assertIsNotNone(response.overscanFit, 

565 msg=f"overscanCorrection overscanIsNotInt Bad overscanFit: {fitType}") 

566 self.assertIsInstance(response.overscanImage, afwImage.ExposureF, 

567 msg=f"overscanCorrection overscanIsNotInt Bad overscanImage: {fitType}") 

568 

569 

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

571 pass 

572 

573 

574def setup_module(module): 

575 lsst.utils.tests.init() 

576 

577 

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

579 lsst.utils.tests.init() 

580 unittest.main()