Coverage for tests/test_butlerFits.py: 15%

269 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-13 01:43 -0800

1# This file is part of obs_base. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://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 <http://www.gnu.org/licenses/>. 

21 

22from __future__ import annotations 

23 

24import os 

25import shutil 

26import tempfile 

27import unittest 

28import unittest.mock 

29import uuid 

30 

31import astropy.table 

32 

33import lsst.afw.cameraGeom.testUtils # for test asserts injected into TestCase 

34import lsst.afw.image 

35import lsst.pex.config 

36import lsst.utils.tests 

37from lsst.afw.fits import readMetadata 

38from lsst.afw.image import LOCAL, PARENT, ExposureFitsReader, MaskedImageFitsReader 

39from lsst.afw.math import flipImage 

40from lsst.daf.base import PropertyList, PropertySet 

41from lsst.daf.butler import Config, DatasetProvenance, DatasetRef, DatasetType, StorageClassFactory 

42from lsst.daf.butler.tests import addDatasetType, makeTestCollection, makeTestRepo 

43from lsst.geom import Box2I, Extent2I, Point2I 

44from lsst.obs.base.exposureAssembler import ExposureAssembler 

45from lsst.obs.base.tests import make_ramp_exposure_trimmed, make_ramp_exposure_untrimmed 

46 

47TESTDIR = os.path.dirname(__file__) 

48 

49BUTLER_CONFIG = """ 

50storageClasses: 

51 ExposureCompositeF: 

52 inheritsFrom: ExposureF 

53datastore: 

54 # Want to check disassembly so can't use InMemory 

55 cls: lsst.daf.butler.datastores.fileDatastore.FileDatastore 

56 formatters: 

57 ExposureCompositeF: lsst.obs.base.formatters.fitsExposure.FitsExposureFormatter 

58 lossless: 

59 formatter: lsst.obs.base.formatters.fitsExposure.FitsExposureFormatter 

60 parameters: 

61 recipe: lossless 

62 uncompressed: 

63 formatter: lsst.obs.base.formatters.fitsExposure.FitsExposureFormatter 

64 parameters: 

65 recipe: noCompression 

66 lossy: 

67 formatter: lsst.obs.base.formatters.fitsExposure.FitsExposureFormatter 

68 parameters: 

69 recipe: lossyBasic 

70 composites: 

71 disassembled: 

72 ExposureCompositeF: True 

73 # Always run with cache disabled. None of these datasets are remote but 

74 # we want to check that URI reading does work properly. 

75 cached: 

76 expiry: 

77 default: false 

78 mode: disabled 

79""" 

80 

81# Components present in the test file 

82COMPONENTS = { 

83 "wcs", 

84 "image", 

85 "mask", 

86 "coaddInputs", 

87 "psf", 

88 "visitInfo", 

89 "variance", 

90 "metadata", 

91 "photoCalib", 

92 "filter", 

93 "validPolygon", 

94 "transmissionCurve", 

95 "detector", 

96 "apCorrMap", 

97 "summaryStats", 

98 "id", 

99} 

100READ_COMPONENTS = { 

101 "bbox", 

102 "xy0", 

103 "dimensions", 

104} 

105 

106 

107class ButlerFitsTests(lsst.utils.tests.TestCase): 

108 """Tests for butler interaction with FITS files.""" 

109 

110 @classmethod 

111 def setUpClass(cls): 

112 """Create a new butler once only.""" 

113 cls.storageClassFactory = StorageClassFactory() 

114 

115 cls.root = tempfile.mkdtemp(dir=TESTDIR) 

116 

117 dataIds = { 

118 "instrument": ["DummyCam"], 

119 "physical_filter": ["d-r"], 

120 "visit": [42, 43, 44], 

121 } 

122 

123 # Ensure that we test in a directory that will include some 

124 # metacharacters 

125 subdir = "sub?#dir" 

126 butlerRoot = os.path.join(cls.root, subdir) 

127 

128 cls.creatorButler = makeTestRepo(butlerRoot, dataIds, config=Config.fromYaml(BUTLER_CONFIG)) 

129 

130 # Create dataset types used by the tests 

131 for datasetTypeName, storageClassName in ( 

132 ("calexp", "ExposureF"), 

133 ("noise", "MaskedImageF"), 

134 ("unknown", "ExposureCompositeF"), 

135 ("testCatalog", "SourceCatalog"), 

136 ("lossless", "ExposureF"), 

137 ("uncompressed", "ExposureF"), 

138 ("lossy", "ExposureF"), 

139 ): 

140 storageClass = cls.storageClassFactory.getStorageClass(storageClassName) 

141 addDatasetType(cls.creatorButler, datasetTypeName, set(dataIds), storageClass) 

142 

143 # And some dataset types that have no dimensions for easy testing 

144 for datasetTypeName, storageClassName in ( 

145 ("ps", "PropertySet"), 

146 ("pl", "PropertyList"), 

147 ("int_exp_trimmed", "ExposureI"), 

148 ("int_exp_untrimmed", "ExposureI"), 

149 ): 

150 storageClass = cls.storageClassFactory.getStorageClass(storageClassName) 

151 addDatasetType(cls.creatorButler, datasetTypeName, {}, storageClass) 

152 

153 @classmethod 

154 def tearDownClass(cls): 

155 if cls.root is not None: 

156 shutil.rmtree(cls.root, ignore_errors=True) 

157 

158 def setUp(self): 

159 self.butler = makeTestCollection(self.creatorButler, uniqueId=self.id()) 

160 

161 def makeExampleCatalog(self) -> lsst.afw.table.SourceCatalog: 

162 catalogPath = os.path.join(TESTDIR, "data", "source_catalog.fits") 

163 return lsst.afw.table.SourceCatalog.readFits(catalogPath) 

164 

165 def assertCatalogEqual( 

166 self, inputCatalog: lsst.afw.table.SourceCatalog, outputCatalog: lsst.afw.table.SourceCatalog 

167 ) -> None: 

168 self.assertIsInstance(outputCatalog, lsst.afw.table.SourceCatalog) 

169 inputTable = inputCatalog.getTable() 

170 inputRecord = inputCatalog[0] 

171 outputTable = outputCatalog.getTable() 

172 outputRecord = outputCatalog[0] 

173 self.assertEqual(inputRecord.getPsfInstFlux(), outputRecord.getPsfInstFlux()) 

174 self.assertEqual(inputRecord.getPsfFluxFlag(), outputRecord.getPsfFluxFlag()) 

175 self.assertEqual( 

176 inputTable.getSchema().getAliasMap().get("slot_Centroid"), 

177 outputTable.getSchema().getAliasMap().get("slot_Centroid"), 

178 ) 

179 self.assertEqual(inputRecord.getCentroid(), outputRecord.getCentroid()) 

180 self.assertFloatsAlmostEqual( 

181 inputRecord.getCentroidErr()[0, 0], outputRecord.getCentroidErr()[0, 0], rtol=1e-6 

182 ) 

183 self.assertFloatsAlmostEqual( 

184 inputRecord.getCentroidErr()[1, 1], outputRecord.getCentroidErr()[1, 1], rtol=1e-6 

185 ) 

186 self.assertEqual( 

187 inputTable.getSchema().getAliasMap().get("slot_Shape"), 

188 outputTable.getSchema().getAliasMap().get("slot_Shape"), 

189 ) 

190 self.assertFloatsAlmostEqual( 

191 inputRecord.getShapeErr()[0, 0], outputRecord.getShapeErr()[0, 0], rtol=1e-6 

192 ) 

193 self.assertFloatsAlmostEqual( 

194 inputRecord.getShapeErr()[1, 1], outputRecord.getShapeErr()[1, 1], rtol=1e-6 

195 ) 

196 self.assertFloatsAlmostEqual( 

197 inputRecord.getShapeErr()[2, 2], outputRecord.getShapeErr()[2, 2], rtol=1e-6 

198 ) 

199 

200 def runFundamentalTypeTest(self, datasetTypeName, entity): 

201 """Put and get the supplied entity and compare.""" 

202 ref = self.butler.put(entity, datasetTypeName) 

203 butler_ps = self.butler.get(ref) 

204 self.assertEqual(butler_ps, entity) 

205 

206 # Break the contact by ensuring that we are writing YAML 

207 uri = self.butler.getURI(ref) 

208 self.assertEqual(uri.getExtension(), ".yaml", f"Check extension of {uri}") 

209 

210 def testFundamentalTypes(self) -> None: 

211 """Ensure that some fundamental stack types round trip.""" 

212 ps = PropertySet() 

213 ps["a.b"] = 5 

214 ps["c.d.e"] = "string" 

215 self.runFundamentalTypeTest("ps", ps) 

216 

217 pl = PropertyList() 

218 pl["A"] = 1 

219 pl.setComment("A", "An int comment") 

220 pl["B"] = "string" 

221 pl.setComment("B", "A string comment") 

222 self.runFundamentalTypeTest("pl", pl) 

223 

224 def _make_provenance(self): 

225 """Return provenance information for testing.""" 

226 prov = DatasetProvenance(quantum_id=uuid.uuid4()) 

227 ref = DatasetRef( 

228 datasetType=self.butler.get_dataset_type("ps"), 

229 dataId=[], 

230 run="run", 

231 ) 

232 prov.add_input(ref) 

233 prov.add_extra_provenance(ref.id, {"EXTRA": 42}) 

234 return prov 

235 

236 def testFitsCatalog(self) -> None: 

237 """Test reading of a FITS catalog.""" 

238 catalog = self.makeExampleCatalog() 

239 dataId = {"visit": 42, "instrument": "DummyCam", "physical_filter": "d-r"} 

240 prov = self._make_provenance() 

241 ref = self.butler.put(catalog, "testCatalog", dataId, provenance=prov) 

242 stored = self.butler.get(ref) 

243 self.assertCatalogEqual(catalog, stored) 

244 self.assertEqual(stored.metadata["LSST BUTLER ID"], str(ref.id)) 

245 self.assertEqual(stored.metadata["LSST BUTLER QUANTUM"], str(prov.quantum_id)) 

246 self.assertEqual(stored.metadata["LSST BUTLER INPUT 0 RUN"], "run") 

247 self.assertEqual(stored.metadata["LSST BUTLER INPUT 0 EXTRA"], 42) 

248 

249 # Override the storage class. 

250 astropy_table = self.butler.get(ref, storageClass="AstropyTable") 

251 self.assertIsInstance(astropy_table, astropy.table.Table) 

252 self.assertEqual(len(astropy_table), len(stored)) 

253 

254 def testExposureCompositePutGetConcrete(self) -> None: 

255 """Test composite with no disassembly.""" 

256 ref = self.runExposureCompositePutGetTest("calexp") 

257 

258 uri = self.butler.getURI(ref) 

259 self.assertTrue(uri.exists(), f"Checking URI {uri} existence") 

260 

261 def testExposureCompositePutGetConcreteAstropy(self) -> None: 

262 """Test composite with no disassembly and astropy component read.""" 

263 with unittest.mock.patch( 

264 "lsst.obs.base.formatters.fitsExposure._ALWAYS_USE_ASTROPY_FOR_COMPONENT_READ", True 

265 ): 

266 self.runExposureCompositePutGetTest("calexp") 

267 

268 def testExposureCompositePutGetVirtual(self) -> None: 

269 """Testing composite disassembly.""" 

270 ref = self.runExposureCompositePutGetTest("unknown") 

271 

272 primary, components = self.butler.getURIs(ref) 

273 self.assertIsNone(primary) 

274 self.assertEqual(set(components), COMPONENTS) 

275 for compName, uri in components.items(): 

276 self.assertTrue(uri.exists(), f"Checking URI {uri} existence for component {compName}") 

277 

278 def runExposureCompositePutGetTest(self, datasetTypeName: str) -> DatasetRef: 

279 example = os.path.join(TESTDIR, "data", "calexp.fits") 

280 exposure = lsst.afw.image.ExposureF(example) 

281 

282 prov = self._make_provenance() 

283 dataId = {"visit": 42, "instrument": "DummyCam", "physical_filter": "d-r"} 

284 ref = self.butler.put(exposure, datasetTypeName, dataId, provenance=prov) 

285 

286 # Get the full thing 

287 composite = self.butler.get(datasetTypeName, dataId) 

288 

289 # Check that provenance has been written. 

290 self.assertEqual(composite.metadata["LSST BUTLER ID"], str(ref.id)) 

291 self.assertEqual(composite.metadata["LSST BUTLER QUANTUM"], str(prov.quantum_id)) 

292 self.assertEqual(composite.metadata["LSST BUTLER INPUT 0 RUN"], "run") 

293 self.assertEqual(composite.metadata["LSST BUTLER INPUT 0 EXTRA"], 42) 

294 

295 # There is no assert for Exposure so just look at maskedImage 

296 self.assertMaskedImagesEqual(composite.maskedImage, exposure.maskedImage) 

297 

298 # Helper for extracting components 

299 assembler = ExposureAssembler(ref.datasetType.storageClass) 

300 

301 # Check all possible components that can be read 

302 allComponents = set() 

303 allComponents.update(COMPONENTS, READ_COMPONENTS) 

304 

305 # Get each component from butler independently 

306 for compName in allComponents: 

307 compTypeName = DatasetType.nameWithComponent(datasetTypeName, compName) 

308 component = self.butler.get(compTypeName, dataId) 

309 

310 reference = assembler.getComponent(exposure, compName) 

311 

312 self.assertIsInstance(component, type(reference), f"Checking type of component {compName}") 

313 

314 if compName in ("image", "variance"): 

315 self.assertImagesEqual(component, reference) 

316 elif compName == "mask": 

317 self.assertMasksEqual(component, reference) 

318 elif compName == "wcs": 

319 self.assertWcsAlmostEqualOverBBox(component, reference, exposure.getBBox()) 

320 elif compName == "coaddInputs": 

321 self.assertEqual( 

322 len(component.visits), len(reference.visits), f"cf visits {component.visits}" 

323 ) 

324 self.assertEqual(len(component.ccds), len(reference.ccds), f"cf CCDs {component.ccds}") 

325 elif compName == "psf": 

326 # Equality for PSF does not work 

327 pass 

328 elif compName == "filter": 

329 # FilterLabel has different values depending whether you load 

330 # it using a ref with full data ID values or not. With a full 

331 # data ID value, it uses the physical_filter data ID value 

332 # instead of reading it from the file. 

333 self.assertTrue(component.physicalLabel == "d-r" or component.physicalLabel == "HSC-I") 

334 elif compName == "id": 

335 self.assertEqual(component, reference, compName) 

336 elif compName == "visitInfo": 

337 self.assertEqual(component, reference, "VisitInfo comparison") 

338 elif compName == "metadata": 

339 # The component metadata has extra fields in it so cannot 

340 # compare directly. 

341 for k, v in reference.items(): 

342 self.assertEqual(component[k], v, f"{compName} key: {k}") 

343 elif compName == "photoCalib": 

344 # This example has a 

345 # "spatially constant with mean: inf error: nan" entry 

346 # which does not compare directly. 

347 self.assertEqual(str(component), str(reference), compName) 

348 self.assertIn("spatially constant with mean: 1.99409", str(component), "Checking photoCalib") 

349 elif compName in ("bbox", "xy0", "dimensions", "validPolygon"): 

350 self.assertEqual(component, reference, compName) 

351 elif compName == "apCorrMap": 

352 self.assertEqual(set(component.keys()), set(reference.keys()), compName) 

353 elif compName == "transmissionCurve": 

354 self.assertEqual( 

355 component.getThroughputAtBounds(), reference.getThroughputAtBounds(), compName 

356 ) 

357 elif compName == "detector": 

358 c_amps = {a.getName() for a in component.getAmplifiers()} 

359 r_amps = {a.getName() for a in reference.getAmplifiers()} 

360 self.assertEqual(c_amps, r_amps, compName) 

361 elif compName == "summaryStats": 

362 self.assertEqual(component.psfSigma, reference.psfSigma, compName) 

363 else: 

364 raise RuntimeError(f"Unexpected component '{compName}' encountered in test") 

365 

366 # Full Exposure with parameters 

367 for origin in (LOCAL, PARENT): 

368 inBBox = Box2I(minimum=Point2I(5, 3), maximum=Point2I(42, 16)) 

369 parameters = {"bbox": inBBox, "origin": origin} 

370 subset = self.butler.get(datasetTypeName, dataId, parameters=parameters) 

371 outBBox = subset.getBBox() 

372 self.assertEqual(inBBox, outBBox) 

373 self.assertImagesEqual(subset.getImage(), exposure.subset(inBBox, origin=LOCAL).getImage()) 

374 

375 return ref 

376 

377 def putFits(self, exposure, datasetTypeName, visit): 

378 """Put different datasetTypes and return information.""" 

379 dataId = {"visit": visit, "instrument": "DummyCam", "physical_filter": "d-r"} 

380 refC = self.butler.put(exposure, datasetTypeName, dataId) 

381 uriC = self.butler.getURI(refC) 

382 stat = os.stat(uriC.ospath) 

383 size = stat.st_size 

384 # We can't use butler's Exposure storage class metadata component, 

385 # because that intentionally strips keywords that science code 

386 # shouldn't ever read (to at least weaken our assumptions that we write 

387 # to FITS). Instead use a lower-level method on the URI for this test. 

388 meta = readMetadata(uriC.ospath) 

389 return meta, size 

390 

391 def testCompression(self): 

392 """Test that we can write compressed and uncompressed FITS.""" 

393 example = os.path.join(TESTDIR, "data", "small.fits") 

394 exposure = lsst.afw.image.ExposureF(example) 

395 

396 # Write a lossless compressed 

397 metaC, sizeC = self.putFits(exposure, "lossless", 42) 

398 self.assertEqual(metaC["TTYPE1"], "COMPRESSED_DATA") 

399 self.assertEqual(metaC["ZCMPTYPE"], "GZIP_2") 

400 

401 # Write an uncompressed FITS file 

402 metaN, sizeN = self.putFits(exposure, "uncompressed", 43) 

403 self.assertNotIn("ZCMPTYPE", metaN) 

404 

405 # Write a lossy-compressed FITS file. 

406 metaL, sizeL = self.putFits(exposure, "lossy", 44) 

407 self.assertEqual(metaL["TTYPE1"], "COMPRESSED_DATA") 

408 # "RICE_ONE" is some bad CFITSIO behavior that we have to tolerate for 

409 # now; it's an old pre-standard key that has become acceptable to at 

410 # least astropy (when reading) that CFITSIO continues to write for the 

411 # dubious purpose of supporting even older readers. 

412 self.assertIn(metaL["ZCMPTYPE"], ("RICE_1", "RICE_ONE")) 

413 

414 self.assertNotEqual(sizeC, sizeN) 

415 # Data file is so small that Lossy and Compressed are dominated 

416 # by the extra compression tables 

417 self.assertEqual(sizeL, sizeC) 

418 

419 def testExposureFormatterAmpParameter(self): 

420 """Test the FitsExposureFormatter implementation of the Exposure 

421 StorageClass's 'amp' and 'detector' parameters. 

422 """ 

423 # Our example exposure file has a realistic detector (looks like HSC), 

424 # but the image itself doesn't match it. So we just load the detector, 

425 # and use it to make our own more useful images and put them. 

426 detector = ExposureFitsReader(os.path.join(TESTDIR, "data", "calexp.fits")).readDetector() 

427 trimmed_full = make_ramp_exposure_trimmed(detector) 

428 untrimmed_full = make_ramp_exposure_untrimmed(detector) 

429 trimmed_ref = self.butler.put(trimmed_full, "int_exp_trimmed") 

430 untrimmed_ref = self.butler.put(untrimmed_full, "int_exp_untrimmed") 

431 for n, amp in enumerate(detector): 

432 # Try to read each amp as it is on disk, with a variety of 

433 # parameters that should all do the same thing. 

434 for amp_parameter in [amp, amp.getName(), n]: 

435 for parameters in [{"amp": amp_parameter}, {"amp": amp_parameter, "detector": detector}]: 

436 with self.subTest(parameters=parameters): 

437 test_trimmed = self.butler.get(trimmed_ref, parameters=parameters) 

438 test_untrimmed = self.butler.get(untrimmed_ref, parameters=parameters) 

439 self.assertImagesEqual(test_trimmed.image, trimmed_full[amp.getBBox()].image) 

440 self.assertImagesEqual(test_untrimmed.image, untrimmed_full[amp.getRawBBox()].image) 

441 self.assertEqual(len(test_trimmed.getDetector()), 1) 

442 self.assertEqual(len(test_untrimmed.getDetector()), 1) 

443 self.assertAmplifiersEqual(test_trimmed.getDetector()[0], amp) 

444 self.assertAmplifiersEqual(test_untrimmed.getDetector()[0], amp) 

445 # Try to read various transformed versions of the original amp, 

446 # to make sure flips and offsets are applied correctly. 

447 # First flip X only. 

448 amp_t1 = amp.rebuild().transform(outFlipX=True).finish() 

449 test_t1_trimmed = self.butler.get(trimmed_ref, parameters={"amp": amp_t1}) 

450 self.assertImagesEqual( 

451 test_t1_trimmed.image, flipImage(trimmed_full[amp.getBBox()].image, flipLR=True, flipTB=False) 

452 ) 

453 self.assertAmplifiersEqual(test_t1_trimmed.getDetector()[0], amp_t1) 

454 test_t1_untrimmed = self.butler.get(untrimmed_ref, parameters={"amp": amp_t1}) 

455 self.assertImagesEqual( 

456 test_t1_untrimmed.image, 

457 flipImage(untrimmed_full[amp.getRawBBox()].image, flipLR=True, flipTB=False), 

458 ) 

459 self.assertAmplifiersEqual(test_t1_trimmed.getDetector()[0], amp_t1) 

460 # Flip Y only. 

461 amp_t2 = amp.rebuild().transform(outFlipY=True).finish() 

462 test_t2_trimmed = self.butler.get(trimmed_ref, parameters={"amp": amp_t2}) 

463 self.assertImagesEqual( 

464 test_t2_trimmed.image, flipImage(trimmed_full[amp.getBBox()].image, flipLR=False, flipTB=True) 

465 ) 

466 self.assertAmplifiersEqual(test_t2_trimmed.getDetector()[0], amp_t2) 

467 test_t2_untrimmed = self.butler.get(untrimmed_ref, parameters={"amp": amp_t2}) 

468 self.assertImagesEqual( 

469 test_t2_untrimmed.image, 

470 flipImage(untrimmed_full[amp.getRawBBox()].image, flipLR=False, flipTB=True), 

471 ) 

472 self.assertAmplifiersEqual(test_t2_trimmed.getDetector()[0], amp_t2) 

473 # Add an XY offset only. 

474 amp_t3 = amp.rebuild().transform(outOffset=Extent2I(5, 4)).finish() 

475 test_t3_trimmed = self.butler.get(trimmed_ref, parameters={"amp": amp_t3}) 

476 self.assertImagesEqual(test_t3_trimmed.image, trimmed_full[amp.getBBox()].image) 

477 self.assertAmplifiersEqual(test_t3_trimmed.getDetector()[0], amp_t3) 

478 test_t3_untrimmed = self.butler.get(untrimmed_ref, parameters={"amp": amp_t3}) 

479 self.assertImagesEqual(test_t3_untrimmed.image, untrimmed_full[amp.getRawBBox()].image) 

480 self.assertAmplifiersEqual(test_t3_trimmed.getDetector()[0], amp_t3) 

481 

482 def testMaskedImageFormatter(self): 

483 """Test that a MaskedImage can be persisted and read from a Butler.""" 

484 # Read in an Exposure as MaskedImage using MaskedImageFitsReader. 

485 reader = MaskedImageFitsReader(os.path.join(TESTDIR, "data", "calexp.fits")) 

486 mi = reader.read() 

487 

488 # Put the MaskedImage into the Butler and get a reference to it. 

489 dataId = {"visit": 42, "instrument": "DummyCam", "physical_filter": "d-r"} 

490 ref = self.butler.put(mi, "noise", dataId) 

491 

492 # Check that the MaskedImage can be retrieved from the butler. 

493 maskedImage = self.butler.get(ref) 

494 self.assertImagesEqual(maskedImage.image, mi.image) 

495 self.assertImagesEqual(maskedImage.mask, mi.mask) 

496 self.assertImagesEqual(maskedImage.variance, mi.variance) 

497 

498 # Get a DeferredDatasetHandle to load parts of the MaskedImage. 

499 handle = self.butler.getDeferred(ref) 

500 

501 for parameters in ( 

502 {}, 

503 {"bbox": reader.readBBox()}, 

504 {"bbox": Box2I(minimum=Point2I(3, 3), maximum=Point2I(21, 16))}, 

505 {"bbox": Box2I(minimum=Point2I(3, 3), maximum=Point2I(21, 16)), "origin": LOCAL}, 

506 ): 

507 bbox = parameters.get("bbox", reader.readBBox()) 

508 with self.subTest(parameters=parameters): 

509 # Check that the reader supports reading sub-regions. 

510 subMaskedImage = reader.read(**parameters) 

511 self.assertImagesEqual(subMaskedImage.image, mi.image[bbox]) 

512 self.assertImagesEqual(subMaskedImage.mask, mi.mask[bbox]) 

513 self.assertImagesEqual(subMaskedImage.variance, mi.variance[bbox]) 

514 

515 # Get a maskedImage within a bounding box from the butler. 

516 subMaskedImage = handle.get(parameters=parameters) 

517 self.assertImagesEqual(subMaskedImage.image, mi.image[bbox]) 

518 self.assertImagesEqual(subMaskedImage.mask, mi.mask[bbox]) 

519 self.assertImagesEqual(subMaskedImage.variance, mi.variance[bbox]) 

520 

521 # Get one component at a time from the butler. 

522 subImage = handle.get(parameters=parameters, component="image") 

523 subMask = handle.get(parameters=parameters, component="mask") 

524 subVariance = handle.get(parameters=parameters, component="variance") 

525 self.assertImagesEqual(subImage, mi.image[bbox]) 

526 self.assertImagesEqual(subMask, mi.mask[bbox]) 

527 self.assertImagesEqual(subVariance, mi.variance[bbox]) 

528 

529 

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

531 unittest.main()