Coverage for tests / test_butlerFits.py: 15%

275 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-26 09:02 +0000

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 

32from astro_metadata_translator import ObservationInfo 

33 

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

35import lsst.afw.image 

36import lsst.pex.config 

37import lsst.utils.tests 

38from lsst.afw.fits import readMetadata 

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

40from lsst.afw.math import flipImage 

41from lsst.daf.base import PropertyList, PropertySet 

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

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

44from lsst.geom import Box2I, Extent2I, Point2I 

45from lsst.obs.base.exposureAssembler import ExposureAssembler 

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

47 

48TESTDIR = os.path.dirname(__file__) 

49 

50BUTLER_CONFIG = """ 

51storageClasses: 

52 ExposureCompositeF: 

53 inheritsFrom: ExposureF 

54datastore: 

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

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

57 formatters: 

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

59 lossless: 

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

61 parameters: 

62 recipe: lossless 

63 uncompressed: 

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

65 parameters: 

66 recipe: noCompression 

67 lossy: 

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

69 parameters: 

70 recipe: lossy16 

71 composites: 

72 disassembled: 

73 ExposureCompositeF: True 

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

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

76 cached: 

77 expiry: 

78 default: false 

79 mode: disabled 

80""" 

81 

82# Components present in the test file 

83COMPONENTS = { 

84 "wcs", 

85 "image", 

86 "mask", 

87 "coaddInputs", 

88 "psf", 

89 "visitInfo", 

90 "variance", 

91 "metadata", 

92 "photoCalib", 

93 "filter", 

94 "validPolygon", 

95 "transmissionCurve", 

96 "detector", 

97 "apCorrMap", 

98 "summaryStats", 

99 "id", 

100} 

101READ_COMPONENTS = { 

102 "bbox", 

103 "xy0", 

104 "dimensions", 

105} 

106 

107 

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

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

110 

111 @classmethod 

112 def setUpClass(cls): 

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

114 cls.storageClassFactory = StorageClassFactory() 

115 

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

117 

118 dataIds = { 

119 "instrument": ["DummyCam"], 

120 "physical_filter": ["HSC-I"], 

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

122 "band": ["300"], 

123 } 

124 

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

126 # metacharacters 

127 subdir = "sub?#dir" 

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

129 

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

131 cls.enterClassContext(cls.creatorButler) 

132 

133 # Create dataset types used by the tests 

134 for datasetTypeName, storageClassName in ( 

135 ("calexp", "ExposureF"), 

136 ("noise", "MaskedImageF"), 

137 ("unknown", "ExposureCompositeF"), 

138 ("testCatalog", "SourceCatalog"), 

139 ("lossless", "ExposureF"), 

140 ("uncompressed", "ExposureF"), 

141 ("lossy", "ExposureF"), 

142 ): 

143 storageClass = cls.storageClassFactory.getStorageClass(storageClassName) 

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

145 

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

147 for datasetTypeName, storageClassName in ( 

148 ("ps", "PropertySet"), 

149 ("pl", "PropertyList"), 

150 ("int_exp_trimmed", "ExposureI"), 

151 ("int_exp_untrimmed", "ExposureI"), 

152 ): 

153 storageClass = cls.storageClassFactory.getStorageClass(storageClassName) 

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

155 

156 @classmethod 

157 def tearDownClass(cls): 

158 if cls.root is not None: 

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

160 

161 def setUp(self): 

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

163 self.enterContext(self.butler) 

164 

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

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

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

168 

169 def assertCatalogEqual( 

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

171 ) -> None: 

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

173 inputTable = inputCatalog.getTable() 

174 inputRecord = inputCatalog[0] 

175 outputTable = outputCatalog.getTable() 

176 outputRecord = outputCatalog[0] 

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

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

179 self.assertEqual( 

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

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

182 ) 

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

184 self.assertFloatsAlmostEqual( 

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

186 ) 

187 self.assertFloatsAlmostEqual( 

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

189 ) 

190 self.assertEqual( 

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

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

193 ) 

194 self.assertFloatsAlmostEqual( 

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

196 ) 

197 self.assertFloatsAlmostEqual( 

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

199 ) 

200 self.assertFloatsAlmostEqual( 

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

202 ) 

203 

204 def runFundamentalTypeTest(self, datasetTypeName, entity): 

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

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

207 butler_ps = self.butler.get(ref) 

208 self.assertEqual(butler_ps, entity) 

209 

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

211 uri = self.butler.getURI(ref) 

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

213 

214 def testFundamentalTypes(self) -> None: 

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

216 ps = PropertySet() 

217 ps["a.b"] = 5 

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

219 self.runFundamentalTypeTest("ps", ps) 

220 

221 pl = PropertyList() 

222 pl["A"] = 1 

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

224 pl["B"] = "string" 

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

226 self.runFundamentalTypeTest("pl", pl) 

227 

228 def _make_provenance(self): 

229 """Return provenance information for testing.""" 

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

231 ref = DatasetRef( 

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

233 dataId=[], 

234 run="run", 

235 ) 

236 prov.add_input(ref) 

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

238 return prov 

239 

240 def testFitsCatalog(self) -> None: 

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

242 catalog = self.makeExampleCatalog() 

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

244 prov = self._make_provenance() 

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

246 stored = self.butler.get(ref) 

247 self.assertCatalogEqual(catalog, stored) 

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

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

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

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

252 

253 # Override the storage class. 

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

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

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

257 

258 def testExposureCompositePutGetConcrete(self) -> None: 

259 """Test composite with no disassembly.""" 

260 ref = self.runExposureCompositePutGetTest("calexp") 

261 

262 uri = self.butler.getURI(ref) 

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

264 

265 def testExposureCompositePutGetConcreteAstropy(self) -> None: 

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

267 with unittest.mock.patch( 

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

269 ): 

270 self.runExposureCompositePutGetTest("calexp") 

271 

272 def testExposureCompositePutGetVirtual(self) -> None: 

273 """Testing composite disassembly.""" 

274 ref = self.runExposureCompositePutGetTest("unknown") 

275 

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

277 self.assertIsNone(primary) 

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

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

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

281 

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

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

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

285 

286 prov = self._make_provenance() 

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

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

289 

290 # Get the full thing 

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

292 

293 # Check that provenance has been written. 

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

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

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

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

298 

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

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

301 

302 # Helper for extracting components 

303 assembler = ExposureAssembler(ref.datasetType.storageClass) 

304 

305 # Check all possible components that can be read 

306 allComponents = set() 

307 allComponents.update(COMPONENTS, READ_COMPONENTS) 

308 

309 # Get each component from butler independently 

310 for compName in allComponents: 

311 compTypeName = DatasetType.nameWithComponent(datasetTypeName, compName) 

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

313 

314 reference = assembler.getComponent(exposure, compName) 

315 

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

317 

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

319 self.assertImagesEqual(component, reference) 

320 elif compName == "mask": 

321 self.assertMasksEqual(component, reference) 

322 elif compName == "wcs": 

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

324 elif compName == "coaddInputs": 

325 self.assertEqual( 

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

327 ) 

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

329 elif compName == "psf": 

330 # Equality for PSF does not work 

331 pass 

332 elif compName == "filter": 

333 # FilterLabel has different values depending whether you load 

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

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

336 # instead of reading it from the file. 

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

338 elif compName == "id": 

339 self.assertEqual(component, reference, compName) 

340 elif compName == "visitInfo": 

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

342 elif compName == "metadata": 

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

344 # compare directly. 

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

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

347 elif compName == "photoCalib": 

348 # This example has a 

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

350 # which does not compare directly. 

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

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

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

354 self.assertEqual(component, reference, compName) 

355 elif compName == "apCorrMap": 

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

357 elif compName == "transmissionCurve": 

358 self.assertEqual( 

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

360 ) 

361 elif compName == "detector": 

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

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

364 self.assertEqual(c_amps, r_amps, compName) 

365 elif compName == "summaryStats": 

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

367 else: 

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

369 

370 # Full Exposure with parameters 

371 for origin in (LOCAL, PARENT): 

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

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

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

375 outBBox = subset.getBBox() 

376 self.assertEqual(inBBox, outBBox) 

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

378 

379 # Check that VisitInfo converts properly. 

380 obs_info = self.butler.get(ref.makeComponentRef("visitInfo"), storageClass="ObservationInfo") 

381 self.assertIsInstance(obs_info, ObservationInfo) 

382 self.assertEqual(obs_info.object, "STRIPE82L", f"{obs_info}") 

383 

384 return ref 

385 

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

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

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

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

390 uriC = self.butler.getURI(refC) 

391 stat = os.stat(uriC.ospath) 

392 size = stat.st_size 

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

394 # because that intentionally strips keywords that science code 

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

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

397 meta = readMetadata(uriC.ospath) 

398 return meta, size 

399 

400 def testCompression(self): 

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

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

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

404 

405 # Write a lossless compressed 

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

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

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

409 

410 # Write an uncompressed FITS file 

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

412 self.assertNotIn("ZCMPTYPE", metaN) 

413 

414 # Write a lossy-compressed FITS file. 

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

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

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

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

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

420 # dubious purpose of supporting even older readers. 

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

422 

423 self.assertNotEqual(sizeC, sizeN) 

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

425 # by the extra compression tables 

426 self.assertEqual(sizeL, sizeC) 

427 

428 def testExposureFormatterAmpParameter(self): 

429 """Test the FitsExposureFormatter implementation of the Exposure 

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

431 """ 

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

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

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

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

436 trimmed_full = make_ramp_exposure_trimmed(detector) 

437 untrimmed_full = make_ramp_exposure_untrimmed(detector) 

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

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

440 for n, amp in enumerate(detector): 

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

442 # parameters that should all do the same thing. 

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

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

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

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

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

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

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

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

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

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

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

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

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

456 # First flip X only. 

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

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

459 self.assertImagesEqual( 

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

461 ) 

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

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

464 self.assertImagesEqual( 

465 test_t1_untrimmed.image, 

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

467 ) 

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

469 # Flip Y only. 

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

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

472 self.assertImagesEqual( 

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

474 ) 

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

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

477 self.assertImagesEqual( 

478 test_t2_untrimmed.image, 

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

480 ) 

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

482 # Add an XY offset only. 

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

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

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

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

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

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

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

490 

491 def testMaskedImageFormatter(self): 

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

493 # Read in an Exposure as MaskedImage using MaskedImageFitsReader. 

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

495 mi = reader.read() 

496 

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

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

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

500 

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

502 maskedImage = self.butler.get(ref) 

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

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

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

506 

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

508 handle = self.butler.getDeferred(ref) 

509 

510 for parameters in ( 

511 {}, 

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

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

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

515 ): 

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

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

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

519 subMaskedImage = reader.read(**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 a maskedImage within a bounding box from the butler. 

525 subMaskedImage = handle.get(parameters=parameters) 

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

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

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

529 

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

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

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

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

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

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

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

537 

538 

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

540 unittest.main()