Coverage for tests / test_butlerFits.py: 15%
275 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 09:02 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 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/>.
22from __future__ import annotations
24import os
25import shutil
26import tempfile
27import unittest
28import unittest.mock
29import uuid
31import astropy.table
32from astro_metadata_translator import ObservationInfo
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
48TESTDIR = os.path.dirname(__file__)
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"""
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}
108class ButlerFitsTests(lsst.utils.tests.TestCase):
109 """Tests for butler interaction with FITS files."""
111 @classmethod
112 def setUpClass(cls):
113 """Create a new butler once only."""
114 cls.storageClassFactory = StorageClassFactory()
116 cls.root = tempfile.mkdtemp(dir=TESTDIR)
118 dataIds = {
119 "instrument": ["DummyCam"],
120 "physical_filter": ["HSC-I"],
121 "visit": [42, 43, 44],
122 "band": ["300"],
123 }
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)
130 cls.creatorButler = makeTestRepo(butlerRoot, dataIds, config=Config.fromYaml(BUTLER_CONFIG))
131 cls.enterClassContext(cls.creatorButler)
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)
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)
156 @classmethod
157 def tearDownClass(cls):
158 if cls.root is not None:
159 shutil.rmtree(cls.root, ignore_errors=True)
161 def setUp(self):
162 self.butler = makeTestCollection(self.creatorButler, uniqueId=self.id())
163 self.enterContext(self.butler)
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)
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 )
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)
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}")
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)
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)
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
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)
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))
258 def testExposureCompositePutGetConcrete(self) -> None:
259 """Test composite with no disassembly."""
260 ref = self.runExposureCompositePutGetTest("calexp")
262 uri = self.butler.getURI(ref)
263 self.assertTrue(uri.exists(), f"Checking URI {uri} existence")
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")
272 def testExposureCompositePutGetVirtual(self) -> None:
273 """Testing composite disassembly."""
274 ref = self.runExposureCompositePutGetTest("unknown")
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}")
282 def runExposureCompositePutGetTest(self, datasetTypeName: str) -> DatasetRef:
283 example = os.path.join(TESTDIR, "data", "calexp.fits")
284 exposure = lsst.afw.image.ExposureF(example)
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)
290 # Get the full thing
291 composite = self.butler.get(datasetTypeName, dataId)
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)
299 # There is no assert for Exposure so just look at maskedImage
300 self.assertMaskedImagesEqual(composite.maskedImage, exposure.maskedImage)
302 # Helper for extracting components
303 assembler = ExposureAssembler(ref.datasetType.storageClass)
305 # Check all possible components that can be read
306 allComponents = set()
307 allComponents.update(COMPONENTS, READ_COMPONENTS)
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)
314 reference = assembler.getComponent(exposure, compName)
316 self.assertIsInstance(component, type(reference), f"Checking type of component {compName}")
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")
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())
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}")
384 return ref
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
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)
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")
410 # Write an uncompressed FITS file
411 metaN, sizeN = self.putFits(exposure, "uncompressed", 43)
412 self.assertNotIn("ZCMPTYPE", metaN)
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"))
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)
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)
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()
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)
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)
507 # Get a DeferredDatasetHandle to load parts of the MaskedImage.
508 handle = self.butler.getDeferred(ref)
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])
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])
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])
539if __name__ == "__main__": 539 ↛ 540line 539 didn't jump to line 540 because the condition on line 539 was never true
540 unittest.main()