Coverage for tests / test_butlerFits.py: 14%

271 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-02 01:18 -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: lossy16 

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": ["HSC-I"], 

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

121 "band": ["300"], 

122 } 

123 

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

125 # metacharacters 

126 subdir = "sub?#dir" 

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

128 

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

130 cls.enterClassContext(cls.creatorButler) 

131 

132 # Create dataset types used by the tests 

133 for datasetTypeName, storageClassName in ( 

134 ("calexp", "ExposureF"), 

135 ("noise", "MaskedImageF"), 

136 ("unknown", "ExposureCompositeF"), 

137 ("testCatalog", "SourceCatalog"), 

138 ("lossless", "ExposureF"), 

139 ("uncompressed", "ExposureF"), 

140 ("lossy", "ExposureF"), 

141 ): 

142 storageClass = cls.storageClassFactory.getStorageClass(storageClassName) 

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

144 

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

146 for datasetTypeName, storageClassName in ( 

147 ("ps", "PropertySet"), 

148 ("pl", "PropertyList"), 

149 ("int_exp_trimmed", "ExposureI"), 

150 ("int_exp_untrimmed", "ExposureI"), 

151 ): 

152 storageClass = cls.storageClassFactory.getStorageClass(storageClassName) 

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

154 

155 @classmethod 

156 def tearDownClass(cls): 

157 if cls.root is not None: 

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

159 

160 def setUp(self): 

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

162 self.enterContext(self.butler) 

163 

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

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

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

167 

168 def assertCatalogEqual( 

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

170 ) -> None: 

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

172 inputTable = inputCatalog.getTable() 

173 inputRecord = inputCatalog[0] 

174 outputTable = outputCatalog.getTable() 

175 outputRecord = outputCatalog[0] 

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

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

178 self.assertEqual( 

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

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

181 ) 

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

183 self.assertFloatsAlmostEqual( 

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

185 ) 

186 self.assertFloatsAlmostEqual( 

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

188 ) 

189 self.assertEqual( 

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

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

192 ) 

193 self.assertFloatsAlmostEqual( 

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

195 ) 

196 self.assertFloatsAlmostEqual( 

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

198 ) 

199 self.assertFloatsAlmostEqual( 

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

201 ) 

202 

203 def runFundamentalTypeTest(self, datasetTypeName, entity): 

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

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

206 butler_ps = self.butler.get(ref) 

207 self.assertEqual(butler_ps, entity) 

208 

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

210 uri = self.butler.getURI(ref) 

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

212 

213 def testFundamentalTypes(self) -> None: 

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

215 ps = PropertySet() 

216 ps["a.b"] = 5 

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

218 self.runFundamentalTypeTest("ps", ps) 

219 

220 pl = PropertyList() 

221 pl["A"] = 1 

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

223 pl["B"] = "string" 

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

225 self.runFundamentalTypeTest("pl", pl) 

226 

227 def _make_provenance(self): 

228 """Return provenance information for testing.""" 

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

230 ref = DatasetRef( 

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

232 dataId=[], 

233 run="run", 

234 ) 

235 prov.add_input(ref) 

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

237 return prov 

238 

239 def testFitsCatalog(self) -> None: 

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

241 catalog = self.makeExampleCatalog() 

242 dataId = {"visit": 42, "instrument": "DummyCam", "physical_filter": "HSC-I"} 

243 prov = self._make_provenance() 

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

245 stored = self.butler.get(ref) 

246 self.assertCatalogEqual(catalog, stored) 

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

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

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

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

251 

252 # Override the storage class. 

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

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

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

256 

257 def testExposureCompositePutGetConcrete(self) -> None: 

258 """Test composite with no disassembly.""" 

259 ref = self.runExposureCompositePutGetTest("calexp") 

260 

261 uri = self.butler.getURI(ref) 

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

263 

264 def testExposureCompositePutGetConcreteAstropy(self) -> None: 

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

266 with unittest.mock.patch( 

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

268 ): 

269 self.runExposureCompositePutGetTest("calexp") 

270 

271 def testExposureCompositePutGetVirtual(self) -> None: 

272 """Testing composite disassembly.""" 

273 ref = self.runExposureCompositePutGetTest("unknown") 

274 

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

276 self.assertIsNone(primary) 

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

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

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

280 

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

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

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

284 

285 prov = self._make_provenance() 

286 dataId = {"visit": 42, "instrument": "DummyCam", "physical_filter": "HSC-I"} 

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

288 

289 # Get the full thing 

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

291 

292 # Check that provenance has been written. 

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

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

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

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

297 

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

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

300 

301 # Helper for extracting components 

302 assembler = ExposureAssembler(ref.datasetType.storageClass) 

303 

304 # Check all possible components that can be read 

305 allComponents = set() 

306 allComponents.update(COMPONENTS, READ_COMPONENTS) 

307 

308 # Get each component from butler independently 

309 for compName in allComponents: 

310 compTypeName = DatasetType.nameWithComponent(datasetTypeName, compName) 

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

312 

313 reference = assembler.getComponent(exposure, compName) 

314 

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

316 

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

318 self.assertImagesEqual(component, reference) 

319 elif compName == "mask": 

320 self.assertMasksEqual(component, reference) 

321 elif compName == "wcs": 

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

323 elif compName == "coaddInputs": 

324 self.assertEqual( 

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

326 ) 

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

328 elif compName == "psf": 

329 # Equality for PSF does not work 

330 pass 

331 elif compName == "filter": 

332 # FilterLabel has different values depending whether you load 

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

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

335 # instead of reading it from the file. 

336 self.assertTrue(component.physicalLabel == "HSC-I" or component.physicalLabel == "HSC-I") 

337 elif compName == "id": 

338 self.assertEqual(component, reference, compName) 

339 elif compName == "visitInfo": 

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

341 elif compName == "metadata": 

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

343 # compare directly. 

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

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

346 elif compName == "photoCalib": 

347 # This example has a 

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

349 # which does not compare directly. 

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

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

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

353 self.assertEqual(component, reference, compName) 

354 elif compName == "apCorrMap": 

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

356 elif compName == "transmissionCurve": 

357 self.assertEqual( 

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

359 ) 

360 elif compName == "detector": 

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

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

363 self.assertEqual(c_amps, r_amps, compName) 

364 elif compName == "summaryStats": 

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

366 else: 

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

368 

369 # Full Exposure with parameters 

370 for origin in (LOCAL, PARENT): 

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

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

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

374 outBBox = subset.getBBox() 

375 self.assertEqual(inBBox, outBBox) 

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

377 

378 return ref 

379 

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

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

382 dataId = {"visit": visit, "instrument": "DummyCam", "physical_filter": "HSC-I"} 

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

384 uriC = self.butler.getURI(refC) 

385 stat = os.stat(uriC.ospath) 

386 size = stat.st_size 

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

388 # because that intentionally strips keywords that science code 

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

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

391 meta = readMetadata(uriC.ospath) 

392 return meta, size 

393 

394 def testCompression(self): 

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

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

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

398 

399 # Write a lossless compressed 

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

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

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

403 

404 # Write an uncompressed FITS file 

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

406 self.assertNotIn("ZCMPTYPE", metaN) 

407 

408 # Write a lossy-compressed FITS file. 

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

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

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

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

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

414 # dubious purpose of supporting even older readers. 

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

416 

417 self.assertNotEqual(sizeC, sizeN) 

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

419 # by the extra compression tables 

420 self.assertEqual(sizeL, sizeC) 

421 

422 def testExposureFormatterAmpParameter(self): 

423 """Test the FitsExposureFormatter implementation of the Exposure 

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

425 """ 

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

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

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

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

430 trimmed_full = make_ramp_exposure_trimmed(detector) 

431 untrimmed_full = make_ramp_exposure_untrimmed(detector) 

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

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

434 for n, amp in enumerate(detector): 

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

436 # parameters that should all do the same thing. 

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

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

439 with self.subTest(parameters=repr(parameters)): 

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

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

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

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

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

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

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

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

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

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

450 # First flip X only. 

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

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

453 self.assertImagesEqual( 

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

455 ) 

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

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

458 self.assertImagesEqual( 

459 test_t1_untrimmed.image, 

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

461 ) 

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

463 # Flip Y only. 

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

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

466 self.assertImagesEqual( 

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

468 ) 

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

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

471 self.assertImagesEqual( 

472 test_t2_untrimmed.image, 

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

474 ) 

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

476 # Add an XY offset only. 

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

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

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

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

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

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

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

484 

485 def testMaskedImageFormatter(self): 

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

487 # Read in an Exposure as MaskedImage using MaskedImageFitsReader. 

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

489 mi = reader.read() 

490 

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

492 dataId = {"visit": 42, "instrument": "DummyCam", "physical_filter": "HSC-I"} 

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

494 

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

496 maskedImage = self.butler.get(ref) 

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

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

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

500 

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

502 handle = self.butler.getDeferred(ref) 

503 

504 for parameters in ( 

505 {}, 

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

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

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

509 ): 

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

511 with self.subTest(parameters=repr(parameters)): 

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

513 subMaskedImage = reader.read(**parameters) 

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

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

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

517 

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

519 subMaskedImage = handle.get(parameters=parameters) 

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

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

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

523 

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

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

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

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

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

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

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

531 

532 

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

534 unittest.main()