Coverage for tests / test_visit_image.py: 15%
268 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-13 08:43 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-13 08:43 +0000
1# This file is part of lsst-images.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12from __future__ import annotations
14import os
15import unittest
16import warnings
17from typing import Any
19import astropy.io.fits
20import astropy.units as u
21import astropy.wcs
22import numpy as np
23from astro_metadata_translator import ObservationInfo
25from lsst.images import (
26 Box,
27 DetectorFrame,
28 Image,
29 MaskPlane,
30 MaskSchema,
31 ObservationSummaryStats,
32 ProjectionAstropyView,
33 TractFrame,
34 VisitImage,
35 get_legacy_visit_image_mask_planes,
36)
37from lsst.images.aperture_corrections import ApertureCorrectionMap, aperture_corrections_to_legacy
38from lsst.images.cameras import Detector
39from lsst.images.fields import ChebyshevField
40from lsst.images.fits import ExtensionKey, FitsOpaqueMetadata
41from lsst.images.json import read as read_json
42from lsst.images.psfs import GaussianPointSpreadFunction, PointSpreadFunction
43from lsst.images.tests import (
44 DP2_VISIT_DETECTOR_DATA_ID,
45 RoundtripFits,
46 TemporaryButler,
47 assert_masked_images_equal,
48 assert_projections_equal,
49 compare_aperture_corrections_to_legacy,
50 compare_detector_to_legacy,
51 compare_visit_image_to_legacy,
52 make_random_projection,
53)
55EXTERNAL_DATA_DIR = os.environ.get("TESTDATA_IMAGES_DIR", None)
56LOCAL_DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
59class VisitImageTestCase(unittest.TestCase):
60 """Basic Tests for VisitImage."""
62 @classmethod
63 def setUpClass(cls) -> None:
64 cls.rng = np.random.default_rng(500)
65 det_frame = DetectorFrame(instrument="Inst", visit=1234, detector=1, bbox=Box.factory[1:4096, 1:4096])
66 cls.projection = make_random_projection(cls.rng, det_frame, Box.factory[1:4096, 1:4096])
67 cls.mask_schema = MaskSchema([MaskPlane("M1", "D1")])
68 cls.obs_info = ObservationInfo(instrument="LSSTCam", detector_num=4)
69 cls.summary_stats = ObservationSummaryStats(psfSigma=2.5, zeroPoint=31.4)
70 cls.gaussian_psf = GaussianPointSpreadFunction(2.5, stamp_size=33, bounds=Box.factory[-10:10, -12:13])
71 cls.aperture_corrections: ApertureCorrectionMap = {
72 "flux1": ChebyshevField(det_frame.bbox, np.array([0.75])),
73 "flux2": ChebyshevField(det_frame.bbox, np.array([0.625])),
74 }
75 cls.detector, _, _ = read_json(Detector, os.path.join(LOCAL_DATA_DIR, "detector.json"))
77 opaque = FitsOpaqueMetadata()
78 hdr = astropy.io.fits.Header()
79 with warnings.catch_warnings():
80 # Silence warnings about long keys becoming HIERARCH.
81 warnings.simplefilter("ignore", category=astropy.io.fits.verify.VerifyWarning)
82 hdr.update({"PLATFORM": "lsstcam", "LSST BUTLER ID": "123456789"})
83 opaque.extract_legacy_primary_header(hdr)
85 cls.image = Image(42, shape=(1024, 1024), unit=u.nJy)
86 cls.variance = Image(5.0, shape=(1024, 1024), unit=u.nJy * u.nJy)
87 # API signature suggests projection and obs_info can be None but they
88 # are required (unless you pass them in via the image plane).
89 cls.visit_image = VisitImage(
90 cls.image,
91 variance=cls.variance,
92 psf=GaussianPointSpreadFunction(2.5, stamp_size=33, bounds=Box.factory[-10:10, -12:13]),
93 mask_schema=cls.mask_schema,
94 projection=cls.projection,
95 obs_info=cls.obs_info,
96 summary_stats=cls.summary_stats,
97 detector=cls.detector,
98 aperture_corrections=cls.aperture_corrections,
99 )
100 cls.visit_image._opaque_metadata = opaque
101 cls.simplest_visit_image = VisitImage(
102 cls.image,
103 psf=GaussianPointSpreadFunction(2.5, stamp_size=33, bounds=Box.factory[-10:10, -12:13]),
104 mask_schema=cls.mask_schema,
105 projection=cls.projection,
106 detector=cls.detector,
107 obs_info=cls.obs_info,
108 )
110 def test_basics(self) -> None:
111 """Test basic constructor patterns."""
112 # Test default fill of variance.
113 visit = self.simplest_visit_image
114 self.assertEqual(visit.variance.array[0, 0], 1.0)
115 self.assertIs(visit[...], visit)
116 self.assertEqual(str(visit), "VisitImage(Image([y=0:1024, x=0:1024], int64), ['M1'])")
117 self.assertEqual(
118 repr(visit),
119 "VisitImage(Image(..., bbox=Box(y=Interval(start=0, stop=1024), x=Interval(start=0, stop=1024)),"
120 " dtype=dtype('int64')), mask_schema=MaskSchema([MaskPlane(name='M1', description='D1')],"
121 " dtype=dtype('uint8')))",
122 )
124 astropy_wcs = visit.astropy_wcs
125 self.assertIsInstance(astropy_wcs, ProjectionAstropyView)
126 approx_wcs = visit.fits_wcs
127 self.assertIsInstance(approx_wcs, astropy.wcs.WCS)
129 with self.assertRaises(TypeError):
130 # Requires a PSF.
131 VisitImage(
132 self.image,
133 mask_schema=self.mask_schema,
134 projection=self.projection,
135 obs_info=self.obs_info,
136 detector=self.detector,
137 )
139 with self.assertRaises(TypeError):
140 # Requires ObservationInfo.
141 VisitImage(
142 self.image,
143 psf=self.gaussian_psf,
144 mask_schema=self.mask_schema,
145 projection=self.projection,
146 detector=self.detector,
147 )
149 with self.assertRaises(TypeError):
150 # Requires a projection.
151 VisitImage(
152 self.image,
153 psf=self.gaussian_psf,
154 mask_schema=self.mask_schema,
155 obs_info=self.obs_info,
156 detector=self.detector,
157 )
159 with self.assertRaises(TypeError):
160 # Requires a detector.
161 VisitImage(
162 self.image,
163 psf=self.gaussian_psf,
164 mask_schema=self.mask_schema,
165 projection=self.projection,
166 obs_info=self.obs_info,
167 )
169 with self.assertRaises(TypeError):
170 # Requires some form of mask.
171 VisitImage(
172 self.image,
173 psf=self.gaussian_psf,
174 projection=self.projection,
175 obs_info=self.obs_info,
176 detector=self.detector,
177 )
179 with self.assertRaises(TypeError):
180 VisitImage(
181 Image(42, shape=(5, 5)),
182 psf=self.gaussian_psf,
183 mask_schema=self.mask_schema,
184 projection=self.projection,
185 obs_info=self.obs_info,
186 detector=self.detector,
187 )
189 # Requires a DetectorFrame.
190 tract_frame = TractFrame(skymap="Skymap", tract=1, bbox=Box.factory[1:10, 1:10])
191 tract_proj = make_random_projection(self.rng, tract_frame, Box.factory[1:4096, 1:4096])
192 with self.assertRaises(TypeError):
193 VisitImage(
194 self.image,
195 projection=tract_proj,
196 psf=self.gaussian_psf,
197 mask_schema=self.mask_schema,
198 obs_info=self.obs_info,
199 detector=self.detector,
200 )
202 # Variance unit mismatch.
203 with self.assertRaises(ValueError):
204 VisitImage(
205 self.image,
206 variance=self.image,
207 psf=self.gaussian_psf,
208 mask_schema=self.mask_schema,
209 projection=self.projection,
210 obs_info=self.obs_info,
211 detector=self.detector,
212 )
214 def test_copy_and_slice(self) -> None:
215 """Test that arrays and components are copied (when not immutable) by
216 'copy' and referenced by 'slice'.
217 """
218 visit = self.visit_image
219 copy = visit.copy()
220 copy.image.array[0, 0] = 30.0
221 self.assertEqual(visit.image.array[0, 0], 42.0)
222 self.assertEqual(copy.image.array[0, 0], 30.0)
223 subvisit = visit[Box.factory[0:5, 0:5]]
224 # Check summary stats.
225 self.assertEqual(copy.summary_stats, visit.summary_stats)
226 self.assertIsNot(copy.summary_stats, visit.summary_stats)
227 self.assertEqual(subvisit.summary_stats, visit.summary_stats)
228 self.assertIs(subvisit.summary_stats, visit.summary_stats)
229 # Check aperture corrections.
230 self.assertEqual(copy.aperture_corrections.keys(), visit.aperture_corrections.keys())
231 self.assertIsNot(copy.aperture_corrections, visit.aperture_corrections)
232 self.assertEqual(subvisit.aperture_corrections.keys(), visit.aperture_corrections.keys())
233 self.assertIs(subvisit.aperture_corrections, visit.aperture_corrections)
235 def test_obs_info(self) -> None:
236 """Check that ObservationInfo has been constructed."""
237 visit = self.visit_image
238 self.assertIsNotNone(visit.obs_info)
239 self.maxDiff = None
240 assert visit.obs_info is not None # for mypy.
241 self.assertEqual(visit.obs_info.instrument, "LSSTCam")
243 def test_summary_stats(self) -> None:
244 """Test the comparisons and attributes of ObservationSummaryStats."""
245 self.assertEqual(self.summary_stats, ObservationSummaryStats(psfSigma=2.5, zeroPoint=31.4))
246 self.assertNotEqual(self.summary_stats, ObservationSummaryStats(psfSigma=2.5))
247 self.assertNotEqual(
248 self.summary_stats, ObservationSummaryStats(psfSigma=2.5, raCorners=(5.2, 5.4, 5.4, 5.2))
249 )
251 def test_read_write(self) -> None:
252 """Test that a visit can round trip through a FITS file."""
253 with RoundtripFits(self, self.visit_image, "VisitImage") as roundtrip:
254 # Check that we're still using the right compression, and that we
255 # wrote WCSs.
256 fits = roundtrip.inspect()
257 self.assertEqual(fits[1].header["ZCMPTYPE"], "GZIP_2")
258 self.assertEqual(fits[1].header["CTYPE1"], "RA---TAN")
259 self.assertEqual(fits[2].header["ZCMPTYPE"], "GZIP_2")
260 self.assertEqual(fits[2].header["CTYPE1"], "RA---TAN")
261 self.assertEqual(fits[3].header["ZCMPTYPE"], "GZIP_2")
262 self.assertEqual(fits[3].header["CTYPE1"], "RA---TAN")
263 # Check a subimage read.
264 subbox = Box.factory[8:13, 9:30]
265 subimage = roundtrip.get(bbox=subbox)
266 assert_masked_images_equal(self, subimage, self.visit_image[subbox], expect_view=False)
267 with self.subTest():
268 self.assertEqual(roundtrip.get("bbox"), self.visit_image.bbox)
269 with self.subTest():
270 obs_info = roundtrip.get("obs_info")
271 self.assertIsInstance(obs_info, ObservationInfo)
272 self.assertEqual(obs_info, self.visit_image.obs_info)
273 with self.subTest():
274 summary_stats = roundtrip.get("summary_stats")
275 self.assertIsInstance(summary_stats, ObservationSummaryStats)
276 self.assertEqual(summary_stats, self.visit_image.summary_stats)
277 with self.subTest():
278 psf = roundtrip.get("psf")
279 self.assertIsInstance(psf, GaussianPointSpreadFunction)
280 self.assertEqual(psf.kernel_bbox, self.gaussian_psf.kernel_bbox)
282 assert_masked_images_equal(self, roundtrip.result, self.visit_image, expect_view=False)
283 # Check that the round-tripped headers are the same (up to card order).
284 self.assertEqual(len(roundtrip.result._opaque_metadata.headers[ExtensionKey()]), 1)
285 self.assertEqual(
286 dict(self.visit_image._opaque_metadata.headers[ExtensionKey()]),
287 dict(roundtrip.result._opaque_metadata.headers[ExtensionKey()]),
288 )
289 self.assertFalse(roundtrip.result._opaque_metadata.headers[ExtensionKey("IMAGE")])
290 self.assertFalse(roundtrip.result._opaque_metadata.headers[ExtensionKey("MASK")])
291 self.assertFalse(roundtrip.result._opaque_metadata.headers[ExtensionKey("VARIANCE")])
292 self.assertEqual(roundtrip.result.obs_info, self.visit_image.obs_info)
293 self.assertIsNotNone(roundtrip.result.summary_stats)
294 self.assertEqual(
295 roundtrip.result.summary_stats.psfSigma,
296 self.visit_image.summary_stats.psfSigma,
297 )
298 self.assertEqual(
299 roundtrip.result.summary_stats.zeroPoint,
300 self.visit_image.summary_stats.zeroPoint,
301 )
304@unittest.skipUnless(EXTERNAL_DATA_DIR is not None, "TESTDATA_IMAGES_DIR is not in the environment.")
305class VisitImageLegacyTestCase(unittest.TestCase):
306 """Tests for the VisitImage class and the basics of the archive system.
308 Requires legacy code.
309 """
311 @classmethod
312 def setUpClass(cls) -> None:
313 assert EXTERNAL_DATA_DIR is not None, "Guaranteed by decorator."
314 cls.filename = os.path.join(EXTERNAL_DATA_DIR, "dp2", "legacy", "visit_image.fits")
315 try:
316 from lsst.afw.image import ExposureFitsReader
318 cls.legacy_exposure = ExposureFitsReader(cls.filename).read()
319 except ImportError:
320 raise unittest.SkipTest("afw not available; cannot read legacy visit images") from None
321 cls.plane_map = plane_map = get_legacy_visit_image_mask_planes()
322 cls.visit_image = VisitImage.read_legacy(
323 cls.filename, preserve_quantization=True, plane_map=plane_map
324 )
326 def test_legacy_errors(self) -> None:
327 """Legacy read failure modes."""
328 with self.assertRaises(ValueError):
329 VisitImage.from_legacy(self.legacy_exposure, instrument="HSC")
330 with self.assertRaises(ValueError):
331 VisitImage.from_legacy(self.legacy_exposure, visit=123456)
332 with self.assertRaises(ValueError):
333 VisitImage.from_legacy(self.legacy_exposure, unit=u.mJy)
334 visit = VisitImage.from_legacy(
335 self.legacy_exposure, instrument="LSSTCam", unit=u.nJy, visit=2025052000177
336 )
337 self.assertEqual(visit.unit, u.nJy)
339 with self.assertRaises(ValueError):
340 VisitImage.read_legacy(self.filename, instrument="HSC")
341 with self.assertRaises(ValueError):
342 VisitImage.read_legacy(self.filename, visit=123456)
344 def test_component_reads(self) -> None:
345 """Test reads of components from legacy file."""
346 visit = VisitImage.read_legacy(self.filename)
347 proj = VisitImage.read_legacy(self.filename, component="projection")
348 assert_projections_equal(self, proj, visit.projection, expect_identity=False)
349 image = VisitImage.read_legacy(self.filename, component="image")
350 self.assertEqual(image, visit.image)
351 self.check_legacy_obs_info(image.obs_info)
352 assert_projections_equal(self, proj, image.projection, expect_identity=False)
353 variance = VisitImage.read_legacy(self.filename, component="variance")
354 self.assertEqual(variance, visit.variance)
355 assert_projections_equal(self, proj, variance.projection, expect_identity=False)
356 self.check_legacy_obs_info(variance.obs_info)
357 mask = VisitImage.read_legacy(self.filename, component="mask")
358 self.assertEqual(mask, visit.mask)
359 assert_projections_equal(self, proj, mask.projection, expect_identity=False)
360 self.check_legacy_obs_info(mask.obs_info)
361 psf = VisitImage.read_legacy(self.filename, component="psf")
362 self.assertIsInstance(psf, PointSpreadFunction)
363 obs_info = VisitImage.read_legacy(self.filename, component="obs_info")
364 self.check_legacy_obs_info(obs_info)
365 summary_stats = VisitImage.read_legacy(self.filename, component="summary_stats")
366 self.assertIsInstance(summary_stats, ObservationSummaryStats)
367 self.assertEqual(summary_stats.nPsfStar, 93)
368 compare_aperture_corrections_to_legacy(
369 self,
370 VisitImage.read_legacy(self.filename, component="aperture_corrections"),
371 self.legacy_exposure.info.getApCorrMap(),
372 visit.bbox,
373 )
374 detector = VisitImage.read_legacy(self.filename, component="detector")
375 compare_detector_to_legacy(self, detector, self.legacy_exposure.getDetector(), is_raw_assembled=True)
377 def check_legacy_obs_info(self, obs_info: ObservationInfo | None) -> None:
378 """Check that an `ObservationInfo` instance is not `None`, and that it
379 matches the one in the legacy test data file.
380 """
381 self.assertIsInstance(obs_info, ObservationInfo)
382 self.assertEqual(obs_info.instrument, "LSSTCam")
383 self.assertEqual(obs_info.detector_num, 85, obs_info)
384 self.assertEqual(obs_info.detector_unique_name, "R21_S11", obs_info)
385 self.assertEqual(obs_info.physical_filter, "r_57", obs_info)
387 def test_obs_info(self) -> None:
388 """Check that ObservationInfo has been constructed."""
389 legacy = VisitImage.from_legacy(self.legacy_exposure, plane_map=self.plane_map)
390 self.assertIsNotNone(legacy.obs_info)
391 self.maxDiff = None
392 self.assertEqual(legacy.obs_info, self.visit_image.obs_info)
393 assert legacy.obs_info is not None # for mypy.
394 self.assertEqual(legacy.obs_info.instrument, "LSSTCam")
395 self.assertEqual(legacy.obs_info.detector_num, 85, legacy.obs_info)
396 self.assertEqual(legacy.obs_info.detector_unique_name, "R21_S11", legacy.obs_info)
397 self.assertEqual(legacy.obs_info.physical_filter, "r_57", legacy.obs_info)
399 def test_aperture_corrections_to_legacy(self) -> None:
400 """Test that we can convert an aperture correction map back to a
401 legacy `lsst.afw.image.ApCorrMap`.
402 """
403 legacy_ap_corr_map = aperture_corrections_to_legacy(self.visit_image.aperture_corrections)
404 compare_aperture_corrections_to_legacy(
405 self, self.visit_image.aperture_corrections, legacy_ap_corr_map, self.visit_image.bbox
406 )
408 def test_read_legacy_headers(self) -> None:
409 """Test that headers were correctly stripped and interpreted in
410 `VisitImage.read_legacy`.
411 """
412 # Check that we read the units from BUNIT.
413 self.assertEqual(self.visit_image.unit, astropy.units.nJy)
414 # Check that the primary header has the keys we want, and none of the
415 # keys we don't want.
416 header = self.visit_image._opaque_metadata.headers[ExtensionKey()]
417 self.assertIn("EXPTIME", header)
418 self.assertEqual(header["PLATFORM"], "lsstcam")
419 self.assertNotIn("LSST BUTLER ID", header)
420 self.assertNotIn("AR HDU", header)
421 self.assertNotIn("A_ORDER", header)
422 # Check that the extension HDUs do not have any custom headers.
423 self.assertFalse(self.visit_image._opaque_metadata.headers[ExtensionKey("IMAGE")])
424 self.assertFalse(self.visit_image._opaque_metadata.headers[ExtensionKey("MASK")])
425 self.assertFalse(self.visit_image._opaque_metadata.headers[ExtensionKey("VARIANCE")])
427 def test_from_legacy_headers(self) -> None:
428 """Test that from_legacy handles headers properly."""
429 legacy = VisitImage.from_legacy(self.legacy_exposure, plane_map=self.plane_map)
430 header = legacy._opaque_metadata.headers[ExtensionKey()]
431 self.assertIn("EXPTIME", header)
432 self.assertEqual(header["PLATFORM"], "lsstcam")
433 self.assertNotIn("LSST BUTLER ID", header)
434 self.assertNotIn("AR HDU", header)
435 self.assertNotIn("A_ORDER", header)
436 # Check that the extension HDUs do not have any custom headers.
437 self.assertFalse(self.visit_image._opaque_metadata.headers[ExtensionKey("IMAGE")])
438 self.assertFalse(self.visit_image._opaque_metadata.headers[ExtensionKey("MASK")])
439 self.assertFalse(self.visit_image._opaque_metadata.headers[ExtensionKey("VARIANCE")])
441 def test_rewrite(self) -> None:
442 """Test that we can rewrite the visit image and preserve both
443 lossy-compressed pixel values and components exactly.
444 """
445 with RoundtripFits(self, self.visit_image, "VisitImage") as roundtrip:
446 # Check that we're still using the right compression, and that we
447 # wrote WCSs.
448 fits = roundtrip.inspect()
449 self.assertEqual(fits[1].header["ZCMPTYPE"], "RICE_1")
450 self.assertEqual(fits[1].header["CTYPE1"], "RA---TAN-SIP")
451 self.assertEqual(fits[2].header["ZCMPTYPE"], "GZIP_2")
452 self.assertEqual(fits[2].header["CTYPE1"], "RA---TAN-SIP")
453 self.assertEqual(fits[3].header["ZCMPTYPE"], "RICE_1")
454 self.assertEqual(fits[3].header["CTYPE1"], "RA---TAN-SIP")
455 # Check a subimage read.
456 subbox = Box.factory[8:13, 9:30]
457 subimage = roundtrip.get(bbox=subbox)
458 assert_masked_images_equal(self, subimage, self.visit_image[subbox], expect_view=False)
459 alternates: dict[str, Any] = {}
460 with self.subTest():
461 self.assertEqual(roundtrip.get("bbox"), self.visit_image.bbox)
462 alternates = {
463 k: roundtrip.get(k)
464 for k in [
465 "projection",
466 "image",
467 "mask",
468 "variance",
469 "psf",
470 "obs_info",
471 "summary_stats",
472 "aperture_corrections",
473 "detector",
474 ]
475 }
476 # Try to do a butler get of a component with storage class
477 # override.
478 with self.subTest():
479 if self.legacy_exposure is not None:
480 import lsst.afw.image
482 # We have VisitInfo available.
483 visit_info = roundtrip.get("obs_info", storageClass="VisitInfo")
484 self.assertIsInstance(visit_info, lsst.afw.image.VisitInfo)
485 self.assertEqual(visit_info.getInstrumentLabel(), "LSSTCam")
486 else:
487 raise unittest.SkipTest("Can not test VisitInfo conversion without afw")
489 assert_masked_images_equal(self, roundtrip.result, self.visit_image, expect_view=False)
490 # Check that the round-tripped headers are the same (up to card order).
491 self.assertEqual(
492 dict(self.visit_image._opaque_metadata.headers[ExtensionKey()]),
493 dict(roundtrip.result._opaque_metadata.headers[ExtensionKey()]),
494 )
495 self.assertFalse(roundtrip.result._opaque_metadata.headers[ExtensionKey("IMAGE")])
496 self.assertFalse(roundtrip.result._opaque_metadata.headers[ExtensionKey("MASK")])
497 self.assertFalse(roundtrip.result._opaque_metadata.headers[ExtensionKey("VARIANCE")])
498 self.assertEqual(roundtrip.result._opaque_metadata.headers[ExtensionKey()]["PLATFORM"], "lsstcam")
499 compare_visit_image_to_legacy(
500 self,
501 roundtrip.result,
502 self.legacy_exposure,
503 expect_view=False,
504 plane_map=self.plane_map,
505 **DP2_VISIT_DETECTOR_DATA_ID,
506 alternates=alternates,
507 )
508 # Check converting from the legacy object in-memory.
509 compare_visit_image_to_legacy(
510 self,
511 VisitImage.from_legacy(self.legacy_exposure, plane_map=self.plane_map),
512 self.legacy_exposure,
513 expect_view=True,
514 plane_map=self.plane_map,
515 **DP2_VISIT_DETECTOR_DATA_ID,
516 )
518 def test_butler_converters(self) -> None:
519 """Test that we can read a VisitImage and its components from a butler
520 dataset written as an `lsst.afw.image.Exposure`.
521 """
522 if self.legacy_exposure is None:
523 raise unittest.SkipTest("lsst.afw.image.afw could not be imported.")
524 with TemporaryButler(legacy="ExposureF") as helper:
525 from lsst.daf.butler import FileDataset
527 helper.butler.ingest(FileDataset(path=self.filename, refs=[helper.legacy]), transfer="symlink")
528 visit_image_ref = helper.legacy.overrideStorageClass("VisitImage")
529 visit_image = helper.butler.get(visit_image_ref)
530 bbox = helper.butler.get(visit_image_ref.makeComponentRef("bbox"))
531 self.assertEqual(bbox, visit_image.bbox)
532 alternates = {
533 k: helper.butler.get(visit_image_ref.makeComponentRef(k))
534 # TODO: including "projection" or "obs_info" here fails because
535 # there's code in daf_butler that expects any component to be
536 # valid for the *internal* storage class, not the requested
537 # one, and that's difficult to fix because it's tied up with
538 # the data ID standardization logic.
539 for k in ["image", "mask", "variance", "psf", "detector"]
540 }
541 compare_visit_image_to_legacy(
542 self,
543 visit_image,
544 self.legacy_exposure,
545 expect_view=False,
546 plane_map=self.plane_map,
547 alternates=alternates,
548 **DP2_VISIT_DETECTOR_DATA_ID,
549 )
552if __name__ == "__main__":
553 unittest.main()