Coverage for python/lsst/images/tests/_checks.py: 10%
381 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-05-27 08:25 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-05-27 08:25 +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
14__all__ = (
15 "arrays_to_legacy_points",
16 "assert_close",
17 "assert_equal_allow_nan",
18 "assert_images_equal",
19 "assert_masked_images_equal",
20 "assert_masks_equal",
21 "assert_projections_equal",
22 "assert_psfs_equal",
23 "check_astropy_wcs_interface",
24 "check_projection",
25 "check_transform",
26 "compare_amplifier_to_legacy",
27 "compare_aperture_corrections_to_legacy",
28 "compare_cell_coadd_to_legacy",
29 "compare_detector_to_legacy",
30 "compare_field_to_legacy",
31 "compare_image_to_legacy",
32 "compare_mask_to_legacy",
33 "compare_masked_image_to_legacy",
34 "compare_observation_summary_stats_to_legacy",
35 "compare_photo_calib_to_legacy",
36 "compare_projection_to_legacy_wcs",
37 "compare_psf_to_legacy",
38 "compare_visit_image_to_legacy",
39 "legacy_coords_to_astropy",
40 "legacy_points_to_xy_array",
41)
43import dataclasses
44import math
45import unittest
46from collections.abc import Mapping
47from typing import TYPE_CHECKING, Any, Literal, cast
49import astropy.units as u
50import astropy.wcs.wcsapi
51import numpy as np
52from astropy.coordinates import SkyCoord
54from .._geom import XY, YX, BoundsError, Box
55from .._image import Image
56from .._mask import Mask, MaskPlane
57from .._masked_image import MaskedImage
58from .._observation_summary_stats import ObservationSummaryStats
59from .._transforms import DetectorFrame, Frame, Projection, SkyFrame, TractFrame, Transform
60from .._visit_image import VisitImage
61from ..aperture_corrections import ApertureCorrectionMap
62from ..cameras import Amplifier, Detector, DetectorType, ReadoutCorner
63from ..cells import CellCoadd, CellIJ, CoaddProvenance
64from ..fields import BaseField, ChebyshevField
65from ..psfs import PointSpreadFunction
67if TYPE_CHECKING:
68 try:
69 from lsst.cell_coadds import MultipleCellCoadd
70 except ImportError:
71 type MultipleCellCoadd = Any # type: ignore[no-redef]
72 try:
73 from lsst.afw.image import PhotoCalib as LegacyPhotoCalib
74 except ImportError:
75 type LegacyPhotoCalib = Any # type: ignore[no-redef]
78def assert_close(
79 tc: unittest.TestCase,
80 a: np.ndarray | u.Quantity | float,
81 b: np.ndarray | u.Quantity | float,
82 **kwargs: Any,
83) -> None:
84 """Test that two arrays, floats, or quantities are almost equal.
86 Parameters
87 ----------
88 tc
89 Test case object with assert methods to use.
90 a
91 Array, float, or quantity to compare.
92 b
93 Array, float, or quantity to compare.
94 **kwargs
95 Forwarded to `astropy.units.allclose`.
96 """
97 tc.assertTrue(u.allclose(a, b, **kwargs), msg=f"{a} != {b}")
100def assert_equal_allow_nan(tc: unittest.TestCase, a: float, b: float) -> None:
101 """Test that two floating point values are equal, with nan == nan."""
102 try:
103 tc.assertEqual(a, b)
104 except AssertionError:
105 if not (math.isnan(a) and math.isnan(b)):
106 raise
109def assert_images_equal(
110 tc: unittest.TestCase,
111 a: Image,
112 b: Image,
113 *,
114 rtol: float = 0.0,
115 atol: float = 0.0,
116 expect_view: bool | Literal["array"] | None = None,
117) -> None:
118 """Assert that two images are equal or nearly equal."""
119 tc.assertEqual(a.bbox, b.bbox)
120 tc.assertEqual(a.unit, b.unit)
121 assert_projections_equal(tc, a.projection, b.projection)
122 if expect_view is not None:
123 tc.assertEqual(np.may_share_memory(a.array, b.array), bool(expect_view))
124 if expect_view == "array":
125 tc.assertEqual(a.metadata, b.metadata)
126 else:
127 tc.assertEqual(a.metadata is b.metadata, expect_view)
128 if not expect_view:
129 assert_close(tc, a.array, b.array, atol=atol, rtol=rtol)
130 tc.assertEqual(a.metadata, b.metadata)
133def assert_masks_equal(tc: unittest.TestCase, a: Mask, b: Mask) -> None:
134 """Assert that two masks are equal or nearly equal."""
135 tc.assertEqual(a.bbox, b.bbox)
136 tc.assertEqual(a.schema, b.schema)
137 tc.assertEqual(a.metadata, b.metadata)
138 assert_projections_equal(tc, a.projection, b.projection)
139 np.testing.assert_array_equal(a.array, b.array)
142def assert_masked_images_equal(
143 tc: unittest.TestCase,
144 a: MaskedImage,
145 b: MaskedImage,
146 *,
147 rtol: float = 0.0,
148 atol: float = 0.0,
149 expect_view: bool | None = None,
150) -> None:
151 """Assert that two masked images are equal or nearly equal."""
152 tc.assertEqual(a.metadata, b.metadata)
153 assert_projections_equal(tc, a.projection, b.projection)
154 assert_images_equal(tc, a.image, b.image, rtol=rtol, atol=atol, expect_view=expect_view)
155 assert_masks_equal(tc, a.mask, b.mask)
156 assert_images_equal(tc, a.variance, b.variance, rtol=rtol, atol=atol, expect_view=expect_view)
159def assert_psfs_equal(
160 tc: unittest.TestCase,
161 psf1: PointSpreadFunction,
162 psf2: PointSpreadFunction,
163 points: YX[np.ndarray] | XY[np.ndarray] | None = None,
164) -> int:
165 """Compare two PSF objets.
167 Parameters
168 ----------
169 tc
170 Test case object with assert methods to use.
171 psf1
172 Point-spread function to test.
173 psf2
174 The other point-spread function to test.
175 points
176 Points to evaluate the PSFs at. If not provided, the intersection of
177 the PSF bounding boxes are used to generate points on a grid.
179 Returns
180 -------
181 `int`
182 The number of points actually tested.
183 """
184 if points is None:
185 points = psf1.bounds.bbox.intersection(psf1.bounds.bbox).meshgrid(3).map(np.ravel)
187 tc.assertEqual(psf1.kernel_bbox, psf2.kernel_bbox)
189 n_points_tested: int = 0
190 for x, y in zip(points.x, points.y):
191 if not psf1.bounds.contains(x=x, y=y):
192 with tc.assertRaises(BoundsError):
193 psf2.compute_kernel_image(x=x, y=y)
194 continue
195 tc.assertEqual(psf1.compute_kernel_image(x=x, y=y), psf2.compute_kernel_image(x=x, y=y))
196 tc.assertEqual(psf1.compute_stellar_bbox(x=x, y=y), psf2.compute_stellar_bbox(x=x, y=y))
197 tc.assertEqual(psf1.compute_stellar_image(x=x, y=y), psf2.compute_stellar_image(x=x, y=y))
198 n_points_tested += 1
199 return n_points_tested
202def compare_image_to_legacy(
203 tc: unittest.TestCase, image: Image, legacy_image: Any, expect_view: bool | None = None
204) -> None:
205 """Compare an `.Image` object to a legacy `lsst.afw.image.Image` object."""
206 tc.assertEqual(image.bbox, Box.from_legacy(legacy_image.getBBox()))
207 if expect_view is not None:
208 tc.assertEqual(np.may_share_memory(image.array, legacy_image.array), expect_view)
209 if not expect_view:
210 np.testing.assert_array_equal(image.array, legacy_image.array)
213def compare_mask_to_legacy(
214 tc: unittest.TestCase, mask: Mask, legacy_mask: Any, plane_map: Mapping[str, MaskPlane] | None = None
215) -> None:
216 """Compare a `.Mask` object to a legacy `lsst.afw.image.Mask` object."""
217 tc.assertEqual(mask.bbox, Box.from_legacy(legacy_mask.getBBox()))
218 if plane_map is None:
219 plane_map = {plane.name: plane for plane in mask.schema if plane is not None}
220 for old_name, new_plane in plane_map.items():
221 np.testing.assert_array_equal(
222 (legacy_mask.array & legacy_mask.getPlaneBitMask(old_name)).astype(bool),
223 mask.get(new_plane.name),
224 )
227def compare_masked_image_to_legacy(
228 tc: unittest.TestCase,
229 masked_image: MaskedImage,
230 legacy_masked_image: Any,
231 *,
232 plane_map: Mapping[str, MaskPlane] | None = None,
233 expect_view: bool | None = None,
234 alternates: Mapping[str, Any] | None = None,
235) -> None:
236 """Compare a `.MaskedImage` object to a legacy `lsst.afw.image.MaskedImage`
237 object.
239 Parameters
240 ----------
241 tc
242 Test case to use for asserts.
243 masked_image
244 New image to test.
245 legacy_masked_image
246 Legacy image to test against.
247 plane_map
248 Mapping between new and legacy mask planes.
249 expect_view
250 Whether to test that the image and variance arrays do or do not share
251 memory.
252 alternates
253 A mapping of other versions of one or more (new) components to also
254 check against the legacy versions of those components.
255 """
256 compare_image_to_legacy(tc, masked_image.image, legacy_masked_image.getImage(), expect_view=expect_view)
257 compare_mask_to_legacy(tc, masked_image.mask, legacy_masked_image.getMask(), plane_map=plane_map)
258 compare_image_to_legacy(
259 tc, masked_image.variance, legacy_masked_image.getVariance(), expect_view=expect_view
260 )
261 if alternates:
262 if image := alternates.get("image"):
263 compare_image_to_legacy(tc, image, legacy_masked_image.getImage(), expect_view=expect_view)
264 if mask := alternates.get("mask"):
265 compare_mask_to_legacy(tc, mask, legacy_masked_image.getMask(), plane_map=plane_map)
266 if variance := alternates.get("variance"):
267 compare_image_to_legacy(tc, variance, legacy_masked_image.getVariance(), expect_view=expect_view)
270def compare_visit_image_to_legacy(
271 tc: unittest.TestCase,
272 visit_image: VisitImage,
273 legacy_exposure: Any,
274 *,
275 plane_map: Mapping[str, MaskPlane] | None = None,
276 expect_view: bool | None = None,
277 instrument: str,
278 visit: int,
279 detector: int,
280 applied_legacy_photo_calib: LegacyPhotoCalib | None = None,
281 alternates: Mapping[str, Any] | None = None,
282) -> None:
283 """Compare a `.VisitImage` object to a legacy `lsst.afw.image.Exposure`
284 object.
286 Parameters
287 ----------
288 tc
289 Test case to use for asserts.
290 visit_image
291 New image to test.
292 legacy_exposure
293 Legacy image to test against.
294 plane_map
295 Mapping between new and legacy mask planes.
296 expect_view
297 Whether to test that the image and variance arrays do or do not share
298 memory.
299 instrument
300 Expected instrument name.
301 visit
302 Expected visit ID.
303 detector
304 Expected detector ID.
305 alternates
306 A mapping of other versions of one or more (new) components to also
307 check against the legacy versions of those components.
308 """
309 compare_masked_image_to_legacy(
310 tc,
311 visit_image,
312 legacy_exposure.getMaskedImage(),
313 plane_map=plane_map,
314 expect_view=expect_view,
315 alternates=alternates,
316 )
317 detector_bbox = Box.from_legacy(legacy_exposure.getDetector().getBBox())
318 compare_projection_to_legacy_wcs(
319 tc,
320 visit_image.projection,
321 legacy_exposure.getWcs(),
322 DetectorFrame(instrument=instrument, visit=visit, detector=detector, bbox=detector_bbox),
323 visit_image.bbox,
324 )
325 tc.assertIs(visit_image.projection, visit_image.mask.projection)
326 tc.assertIs(visit_image.projection, visit_image.variance.projection)
327 compare_psf_to_legacy(tc, visit_image.psf, legacy_exposure.getPsf())
328 compare_observation_summary_stats_to_legacy(
329 tc, visit_image.summary_stats, legacy_exposure.info.getSummaryStats()
330 )
331 compare_detector_to_legacy(tc, visit_image.detector, legacy_exposure.getDetector(), is_raw_assembled=True)
332 # Make a tiny box for Field comparisons that need to make arrays; that can
333 # get expensive otherwisre.
334 tiny_bbox = detector_bbox.local[2:4, 3:6]
335 compare_aperture_corrections_to_legacy(
336 tc, visit_image.aperture_corrections, legacy_exposure.info.getApCorrMap(), tiny_bbox
337 )
338 compare_photo_calib_to_legacy(
339 tc,
340 visit_image.photometric_scaling,
341 legacy_exposure.info.getPhotoCalib(),
342 applied_legacy_photo_calib=applied_legacy_photo_calib,
343 subimage_bbox=tiny_bbox,
344 )
345 if alternates:
346 if (bbox := alternates.get("bbox")) is not None:
347 tc.assertEqual(bbox, visit_image.bbox)
348 if projection := alternates.get("projection"):
349 compare_projection_to_legacy_wcs(
350 tc,
351 projection,
352 legacy_exposure.getWcs(),
353 DetectorFrame(instrument=instrument, visit=visit, detector=detector, bbox=detector_bbox),
354 visit_image.bbox,
355 )
356 if psf := alternates.get("psf"):
357 compare_psf_to_legacy(tc, psf, legacy_exposure.getPsf())
358 if summary_stats := alternates.get("summary_stats"):
359 compare_observation_summary_stats_to_legacy(
360 tc, summary_stats, legacy_exposure.info.getSummaryStats()
361 )
362 if detector_obj := alternates.get("detector"):
363 compare_detector_to_legacy(tc, detector_obj, legacy_exposure.getDetector(), is_raw_assembled=True)
364 if obs_info := alternates.get("obs_info"):
365 visitInfo = legacy_exposure.visitInfo
366 tc.assertEqual(obs_info.instrument, visitInfo.getInstrumentLabel())
367 if aperture_corrections := alternates.get("aperture_corrections"):
368 compare_aperture_corrections_to_legacy(
369 tc, aperture_corrections, legacy_exposure.info.getApCorrMap(), tiny_bbox
370 )
371 if (photometric_scaling := alternates.get("photometic_scaling", ...)) is not ...:
372 compare_photo_calib_to_legacy(
373 tc,
374 photometric_scaling,
375 legacy_exposure.info.getPhotoCalib(),
376 applied_legacy_photo_calib=applied_legacy_photo_calib,
377 subimage_bbox=tiny_bbox,
378 )
381def compare_photo_calib_to_legacy(
382 tc: unittest.TestCase,
383 photometric_scaling: BaseField | None,
384 legacy_photo_calib: LegacyPhotoCalib,
385 *,
386 applied_legacy_photo_calib: LegacyPhotoCalib | None = None,
387 subimage_bbox: Box,
388) -> None:
389 if legacy_photo_calib._isConstant:
390 if legacy_photo_calib.getCalibrationMean() == 1.0:
391 if applied_legacy_photo_calib is None:
392 tc.assertIsNone(photometric_scaling)
393 return
394 else:
395 legacy_photo_calib = applied_legacy_photo_calib
396 if legacy_photo_calib._isConstant:
397 assert isinstance(photometric_scaling, ChebyshevField)
398 assert_close(
399 tc, photometric_scaling.coefficients, np.array([[legacy_photo_calib.getCalibrationMean()]])
400 )
401 else:
402 assert photometric_scaling is not None
403 compare_field_to_legacy(
404 tc,
405 photometric_scaling / legacy_photo_calib.getCalibrationMean(),
406 legacy_photo_calib.computeScaledCalibration(),
407 subimage_bbox,
408 )
411def compare_cell_coadd_to_legacy(
412 tc: unittest.TestCase,
413 cell_coadd: CellCoadd,
414 legacy_cell_coadd: MultipleCellCoadd,
415 *,
416 tract_bbox: Box,
417 plane_map: Mapping[str, MaskPlane] | None = None,
418 alternates: Mapping[str, Any] | None = None,
419 psf_points: XY[np.ndarray] | YX[np.ndarray] | None = None,
420) -> None:
421 """Compare a `.cells.CellCoadd` object to a legacy
422 `lsst.cell_coadds.MultipleCellCoadd` object.
424 Parameters
425 ----------
426 tc
427 Test case to use for asserts.
428 cell_coadd
429 New coadd to test.
430 legacy_cell_coadd
431 Legacy coadd to test against.
432 tract_bbox
433 Bounding box of the full tract.
434 psf_points
435 Points to use to compare the PSFs.
436 plane_map
437 Mapping between new and legacy mask planes.
438 alternates
439 A mapping of other versions of one or more (new) components to also
440 check against the legacy versions of those components.
441 """
442 legacy_stitched = legacy_cell_coadd.stitch(cell_coadd.bbox.to_legacy())
443 compare_image_to_legacy(tc, cell_coadd.image, legacy_stitched.image, expect_view=False)
444 compare_mask_to_legacy(tc, cell_coadd.mask, legacy_stitched.mask, plane_map=plane_map)
445 compare_image_to_legacy(tc, cell_coadd.variance, legacy_stitched.variance, expect_view=False)
446 if legacy_stitched.mask_fractions is not None:
447 compare_image_to_legacy(
448 tc, cell_coadd.mask_fractions["rejected"], legacy_stitched.mask_fractions, expect_view=False
449 )
450 for n in range(legacy_stitched.n_noise_realizations):
451 compare_image_to_legacy(
452 tc, cell_coadd.noise_realizations[n], legacy_stitched.noise_realizations[n], expect_view=False
453 )
454 tc.assertEqual(cell_coadd.skymap, legacy_stitched.identifiers.skymap)
455 tc.assertEqual(cell_coadd.tract, legacy_stitched.identifiers.tract)
456 tc.assertEqual(cell_coadd.patch.index.x, legacy_stitched.identifiers.patch.x)
457 tc.assertEqual(cell_coadd.patch.index.y, legacy_stitched.identifiers.patch.y)
458 tc.assertEqual(cell_coadd.band, legacy_stitched.identifiers.band)
459 tc.assertTrue(tract_bbox.contains(cell_coadd.patch.outer_bbox))
460 tc.assertTrue(cell_coadd.patch.outer_bbox.contains(cell_coadd.patch.inner_bbox))
461 tc.assertTrue(cell_coadd.patch.outer_bbox.contains(cell_coadd.bbox))
462 tc.assertEqual(cell_coadd.unit, u.Unit(legacy_cell_coadd.common.units.value))
463 tc.assertTrue(cell_coadd.bounds.bbox.contains(cell_coadd.bbox))
464 tc.assertTrue(cell_coadd.grid.bbox.contains(cell_coadd.bbox))
465 compare_projection_to_legacy_wcs(
466 tc,
467 cell_coadd.projection,
468 legacy_cell_coadd.wcs,
469 TractFrame(
470 skymap=legacy_cell_coadd.identifiers.skymap,
471 tract=legacy_cell_coadd.identifiers.tract,
472 bbox=tract_bbox,
473 ),
474 cell_coadd.bbox,
475 is_fits=True,
476 )
477 tc.assertIs(cell_coadd.projection, cell_coadd.mask.projection)
478 tc.assertIs(cell_coadd.projection, cell_coadd.variance.projection)
479 compare_psf_to_legacy(
480 tc, cell_coadd.psf, legacy_stitched.psf, expect_legacy_raise_on_out_of_bounds=True, points=psf_points
481 )
482 compare_cell_coadd_provenance_to_legacy(tc, cell_coadd.provenance, legacy_cell_coadd)
483 if alternates:
484 if projection := alternates.get("projection"):
485 compare_projection_to_legacy_wcs(
486 tc,
487 projection,
488 legacy_stitched.wcs,
489 TractFrame(
490 skymap=legacy_cell_coadd.identifiers.skymap,
491 tract=legacy_cell_coadd.identifiers.tract,
492 bbox=tract_bbox,
493 ),
494 cell_coadd.bbox,
495 is_fits=True,
496 )
497 if psf := alternates.get("psf"):
498 compare_psf_to_legacy(tc, psf, legacy_stitched.psf, points=psf_points)
499 if provenance := alternates.get("provenance"):
500 compare_cell_coadd_provenance_to_legacy(tc, provenance, legacy_cell_coadd)
503def compare_cell_coadd_provenance_to_legacy(
504 tc: unittest.TestCase, provenance: CoaddProvenance, legacy_cell_coadd: MultipleCellCoadd
505) -> None:
506 """Compare a `.cells.CoaddProvenance` object to a legacy
507 `lsst.cell_coadds.MultipleCellCoadd` object.
509 Parameters
510 ----------
511 tc
512 Test case to use for asserts.
513 provenance
514 New provenance object to test.
515 legacy_cell_coadd
516 Legacy coadd to test against.
517 """
518 from lsst.cell_coadds import ObservationIdentifiers
520 for legacy_cell in legacy_cell_coadd.cells.values():
521 cell_index = CellIJ.from_legacy(legacy_cell.identifiers.cell)
522 prov = provenance[cell_index]
523 legacy_table = astropy.table.Table(
524 rows=[
525 [
526 ids.instrument,
527 ids.visit,
528 ids.detector,
529 ids.day_obs,
530 ids.physical_filter,
531 legacy_input.overlaps_center,
532 legacy_input.overlap_fraction,
533 legacy_input.weight,
534 legacy_input.psf_shape.getIxx(),
535 legacy_input.psf_shape.getIyy(),
536 legacy_input.psf_shape.getIxy(),
537 legacy_input.psf_shape_flag,
538 ]
539 for ids, legacy_input in legacy_cell.inputs.items()
540 ],
541 dtype=[
542 np.object_,
543 np.uint64,
544 np.uint16,
545 np.uint32,
546 np.object_,
547 np.bool_,
548 np.float64,
549 np.float64,
550 np.float64,
551 np.float64,
552 np.float64,
553 np.bool_,
554 ],
555 names=[
556 "instrument",
557 "visit",
558 "detector",
559 "day_obs",
560 "physical_filter",
561 "overlaps_center",
562 "overlap_fraction",
563 "weight",
564 "psf_shape_xx",
565 "psf_shape_yy",
566 "psf_shape_xy",
567 "psf_shape_flag",
568 ],
569 )
570 # For a single cell all 'inputs' are also 'contributions'.
571 tc.assertEqual(len(legacy_cell.inputs), len(prov.inputs))
572 tc.assertEqual(len(legacy_cell.inputs), len(prov.contributions))
573 prov.inputs.sort(["instrument", "visit", "detector"])
574 prov.contributions.sort(["instrument", "visit", "detector"])
575 legacy_table.sort(["instrument", "visit", "detector"])
576 np.testing.assert_array_equal(prov.inputs["instrument"], prov.contributions["instrument"])
577 np.testing.assert_array_equal(prov.inputs["visit"], prov.contributions["visit"])
578 np.testing.assert_array_equal(prov.inputs["detector"], prov.contributions["detector"])
579 np.testing.assert_array_equal(prov.inputs["instrument"], legacy_table["instrument"])
580 np.testing.assert_array_equal(prov.inputs["visit"], legacy_table["visit"])
581 np.testing.assert_array_equal(prov.inputs["detector"], legacy_table["detector"])
582 np.testing.assert_array_equal(prov.inputs["physical_filter"], legacy_table["physical_filter"])
583 np.testing.assert_array_equal(prov.inputs["day_obs"], legacy_table["day_obs"])
584 np.testing.assert_array_equal(prov.contributions["overlaps_center"], legacy_table["overlaps_center"])
585 np.testing.assert_array_equal(
586 prov.contributions["overlap_fraction"], legacy_table["overlap_fraction"]
587 )
588 np.testing.assert_array_equal(prov.contributions["weight"], legacy_table["weight"])
589 np.testing.assert_array_equal(prov.contributions["psf_shape_xx"], legacy_table["psf_shape_xx"])
590 np.testing.assert_array_equal(prov.contributions["psf_shape_yy"], legacy_table["psf_shape_yy"])
591 np.testing.assert_array_equal(prov.contributions["psf_shape_xy"], legacy_table["psf_shape_xy"])
592 np.testing.assert_array_equal(prov.contributions["psf_shape_flag"], legacy_table["psf_shape_flag"])
593 for row in prov.inputs:
594 polygon_key = ObservationIdentifiers(**{k: row[k] for k in row.keys() if k != "polygon"})
595 legacy_polygon = legacy_cell_coadd.common.visit_polygons[polygon_key]
596 tc.assertEqual(legacy_polygon, row["polygon"].to_legacy())
599def compare_psf_to_legacy(
600 tc: unittest.TestCase,
601 psf: PointSpreadFunction,
602 legacy_psf: Any,
603 points: YX[np.ndarray] | XY[np.ndarray] | None = None,
604 expect_legacy_raise_on_out_of_bounds: bool = False,
605) -> int:
606 """Compare a PSF model object to its legacy interface.
608 Parameters
609 ----------
610 tc
611 Test case object with assert methods to use.
612 psf
613 Point-spread function to test.
614 legacy_psf
615 Legacy `lsst.afw.detection.Psf` instance to compare with.
616 points
617 Points to evaluate the PSFs at. If not provided, the intersection of
618 the PSF bounding boxes are used to generate points on a grid.
619 expect_legacy_raise_on_out_of_bounds
620 If `True`, expect ``legacy_psf`` to raise
621 `lsst.afw.detection.InvalidPsfError` when evaluated at a position
622 considered out-of-bounds by ``psf``.
624 Returns
625 -------
626 `int`
627 The number of points actually tested.
628 """
629 from lsst.afw.detection import InvalidPsfError
631 if points is None:
632 points = psf.bounds.bbox.meshgrid(n=3).map(np.ravel)
633 legacy_points = arrays_to_legacy_points(points.x, points.y)
634 n_points_tested: int = 0
635 for p in legacy_points:
636 if not psf.bounds.contains(x=p.x, y=p.y):
637 if expect_legacy_raise_on_out_of_bounds:
638 with tc.assertRaises(InvalidPsfError):
639 legacy_psf.computeKernelImage(p)
640 continue
641 tc.assertEqual(psf.kernel_bbox, Box.from_legacy(legacy_psf.computeKernelBBox(p)))
642 tc.assertEqual(
643 psf.compute_kernel_image(x=p.x, y=p.y), Image.from_legacy(legacy_psf.computeKernelImage(p))
644 )
645 tc.assertEqual(
646 psf.compute_stellar_bbox(x=p.x, y=p.y), Box.from_legacy(legacy_psf.computeImageBBox(p))
647 )
648 tc.assertEqual(psf.compute_stellar_image(x=p.x, y=p.y), Image.from_legacy(legacy_psf.computeImage(p)))
649 n_points_tested += 1
650 return n_points_tested
653def compare_field_to_legacy(
654 tc: unittest.TestCase,
655 field: BaseField,
656 legacy_field: Any,
657 subimage_bbox: Box,
658) -> None:
659 """Test a Field object by comparing it to an equivalent
660 `lsst.afw.math.BoundedField`.
662 Parameters
663 ----------
664 tc
665 Test case object with assert methods to use.
666 field
667 Field to test.
668 legacy_field : ``lsst.afw.math.BoundedField``
669 Equivalent legacy bounded field.
670 subimage_bbox
671 Bounding box for full-image tests.
672 """
673 tc.assertEqual(field.bounds.bbox, Box.from_legacy(legacy_field.getBBox()))
674 # Pixel coordinates to test the numpy array interface with.
675 pixel_xy = field.bounds.bbox.meshgrid(n=5).map(np.ravel)
676 assert_close(tc, field(x=pixel_xy.x, y=pixel_xy.y), legacy_field.evaluate(pixel_xy.x, pixel_xy.y))
677 legacy_image_1 = Image(0, bbox=subimage_bbox, dtype=np.float64).to_legacy()
678 legacy_field.addToImage(legacy_image_1, overlapOnly=True)
679 assert_images_equal(
680 tc, field.render(subimage_bbox), Image.from_legacy(legacy_image_1, unit=field.unit), rtol=1e-13
681 )
684def compare_aperture_corrections_to_legacy(
685 tc: unittest.TestCase,
686 aperture_corrections: ApertureCorrectionMap,
687 legacy_ap_corr_map: Any,
688 subimage_bbox: Box,
689) -> None:
690 """Test an aperture correction `dict` by comparing it to an equivalent
691 `lsst.afw.image.ApCorrMap`.
693 Parameters
694 ----------
695 tc
696 Test case object with assert methods to use.
697 aperture_corrections
698 Dictionary to test.
699 legacy_ap_corr_map : ``lsst.afw.image.ApCorrMap``
700 Equivalent legacy aperture correction map.
701 subimage_bbox
702 Bounding box for full-image tests.
703 """
704 tc.assertEqual(aperture_corrections.keys(), set(legacy_ap_corr_map.keys()))
705 for name, field in aperture_corrections.items():
706 compare_field_to_legacy(tc, field, legacy_ap_corr_map[name], subimage_bbox)
709def compare_observation_summary_stats_to_legacy(
710 tc: unittest.TestCase,
711 summary_stats: ObservationSummaryStats,
712 legacy_summary_stats: Any,
713) -> None:
714 """Test an ObservationSummaryStats object by comparing it to an equivalent
715 `lsst.afw.image.ExposureSummaryStats`.
717 Parameters
718 ----------
719 tc
720 Test case object with assert methods to use.
721 summary_stats
722 Struct to test.
723 legacy : ``lsst.afw.image.ExposureSummaryStats``
724 Equivalent legacy struct.
725 """
726 for field in dataclasses.fields(legacy_summary_stats):
727 a = getattr(legacy_summary_stats, field.name)
728 b = getattr(summary_stats, field.name)
729 if isinstance(b, tuple):
730 for ai, bi in zip(a, b):
731 tc.assertTrue(ai == bi or (math.isnan(ai) and math.isnan(bi)), f"{field.name}: {a} != {b}")
732 else:
733 tc.assertTrue(a == b or (math.isnan(a) and math.isnan(b)), f"{field.name}: {a} != {b}")
736def compare_projection_to_legacy_wcs[F: Frame](
737 tc: unittest.TestCase,
738 projection: Projection[F],
739 legacy_wcs: Any,
740 pixel_frame: F,
741 subimage_bbox: Box,
742 is_fits: bool = False,
743) -> None:
744 """Test a Projection object by comparing it to an equivalent
745 `lsst.afw.geom.SkyWcs`.
747 Parameters
748 ----------
749 tc
750 Test case object with assert methods to use.
751 projection
752 Projection to test.
753 legacy_wcs : ``lsst.afw.geom.SkyWcs``
754 Equivalent legacy WCS.
755 pixel_frame
756 Expected pixel frame for the projection.
757 subimage_bbox
758 Bounding box of points to generate for tests.
759 is_fits
760 Whether this projection is expected to be exactly representable as a
761 FIT WCS. If `False` it is assumed to have a FITS approximation
762 attached instead.
763 """
764 # Pixel coordinates to test on over the subimage region of interest:
765 pixel_xy = subimage_bbox.meshgrid(step=50).map(np.ravel)
766 # Array indices of those pixel values (subtract off bbox starts):
767 subimage_array_xy = XY(x=pixel_xy.x - subimage_bbox.x.start, y=pixel_xy.y - subimage_bbox.y.start)
768 sky_coords = legacy_coords_to_astropy(
769 legacy_wcs.pixelToSky(arrays_to_legacy_points(pixel_xy.x, pixel_xy.y))
770 )
771 # Test transforming with the Projection itself, which also tests its
772 # nested Transform and an Astropy High-Level WCS view with no origin
773 # change.
774 check_projection(tc, projection, pixel_xy, sky_coords, pixel_frame)
775 # Also test the Astropy High-Level WCS view with an origin change to
776 # array indices.
777 check_astropy_wcs_interface(
778 tc, projection.as_astropy(subimage_bbox), subimage_array_xy, sky_coords, pixel_atol=1e-5
779 )
780 if is_fits:
781 fits_wcs = projection.as_fits_wcs(subimage_bbox, allow_approximation=True)
782 assert fits_wcs is not None
783 check_astropy_wcs_interface(tc, fits_wcs, subimage_array_xy, sky_coords, pixel_atol=1e-5)
784 # Use that FITS approximation to check that we can make a
785 # Projection from a FITS WCS, too.
786 fits_projection = Projection.from_fits_wcs(fits_wcs, pixel_frame)
787 check_projection(
788 tc,
789 fits_projection,
790 subimage_array_xy,
791 sky_coords,
792 pixel_frame,
793 pixel_atol=1e-5,
794 )
795 # We want Projections we create from a FITS WCS to be backed by an
796 # AST FrameSet so we can convert them into legacy
797 # `lsst.afw.geom.SkyWcs` objects if desired.
798 tc.assertIn("Begin FrameSet", fits_projection.show())
799 else:
800 tc.assertIsNone(projection.as_fits_wcs(subimage_bbox, allow_approximation=False))
801 # The legacy SkyWcs should instead have a FITS approximation
802 # attached; run the same tests on that.
803 assert projection.fits_approximation is not None
804 compare_projection_to_legacy_wcs(
805 tc,
806 projection.fits_approximation,
807 legacy_wcs.getFitsApproximation(),
808 pixel_frame,
809 subimage_bbox,
810 is_fits=True,
811 )
814def check_transform[I: Frame, O: Frame](
815 tc: unittest.TestCase,
816 transform: Transform[I, O],
817 input_xy: XY[np.ndarray],
818 output_xy: XY[np.ndarray],
819 in_frame: Frame,
820 out_frame: Frame,
821 *,
822 check_inverted: bool = True,
823 in_atol: u.Quantity | None = None,
824 out_atol: u.Quantity | None = None,
825) -> None:
826 """Test Transform against known arrays of input and output points.
828 Parameters
829 ----------
830 tc
831 Test case object with assert methods to use.
832 transform
833 Transform to test.
834 input_xy
835 Arrays of input points.
836 output_xy
837 Arrays of output points.
838 in_frame
839 Expected input frame.
840 out_frame
841 Expected output frame.
842 check_inverted
843 If `True`, recurse (once) to test the inverse transform.
844 in_atol
845 Expected absolute precision of input points.
846 out_atol
847 Expected absolute precision of output points.
848 """
849 tc.assertEqual(transform.in_frame, in_frame)
850 tc.assertEqual(transform.out_frame, out_frame)
851 in_atol_v = in_atol.to_value(in_frame.unit) if in_atol is not None else None
852 out_atol_v = out_atol.to_value(out_frame.unit) if out_atol is not None else None
853 # Test array interfaces.
854 test_output_xy = transform.apply_forward(x=input_xy.x, y=input_xy.y)
855 assert_close(tc, test_output_xy.x, output_xy.x, atol=out_atol_v)
856 assert_close(tc, test_output_xy.y, output_xy.y, atol=out_atol_v)
857 test_input_xy = transform.apply_inverse(x=output_xy.x, y=output_xy.y)
858 assert_close(tc, test_input_xy.x, input_xy.x, atol=in_atol_v)
859 assert_close(tc, test_input_xy.y, input_xy.y, atol=in_atol_v)
860 # Test scalar interfaces with numpy scalars.
861 for input_x, input_y, output_x, output_y in zip(input_xy.x, input_xy.y, output_xy.x, output_xy.y):
862 assert_close(tc, transform.apply_forward(x=input_x, y=input_y).x, output_x, atol=out_atol_v)
863 assert_close(tc, transform.apply_forward(x=input_x, y=input_y).y, output_y, atol=out_atol_v)
864 assert_close(tc, transform.apply_inverse(x=output_x, y=output_y).x, input_x, atol=in_atol_v)
865 assert_close(tc, transform.apply_inverse(x=output_x, y=output_y).y, input_y, atol=in_atol_v)
866 # Test quantity array interfaces.
867 input_q_xy = XY(x=input_xy.x * transform.in_frame.unit, y=input_xy.y * transform.in_frame.unit)
868 output_q_xy = XY(x=output_xy.x * transform.out_frame.unit, y=output_xy.y * transform.out_frame.unit)
869 test_output_q_xy = transform.apply_forward_q(x=input_q_xy.x, y=input_q_xy.y)
870 assert_close(tc, test_output_q_xy.x, output_q_xy.x, atol=out_atol)
871 assert_close(tc, test_output_q_xy.y, output_q_xy.y, atol=out_atol)
872 test_input_q_xy = transform.apply_inverse_q(x=output_q_xy.x, y=output_q_xy.y)
873 assert_close(tc, test_input_q_xy.x, input_q_xy.x, atol=in_atol)
874 assert_close(tc, test_input_q_xy.y, input_q_xy.y, atol=in_atol)
875 # Test quantity scalar interfaces.
876 for input_q_x, input_q_y, output_q_x, output_q_y in zip(
877 input_q_xy.x, input_q_xy.y, output_q_xy.x, output_q_xy.y
878 ):
879 assert_close(tc, transform.apply_forward_q(x=input_q_x, y=input_q_y).x, output_q_x, atol=out_atol)
880 assert_close(tc, transform.apply_forward_q(x=input_q_x, y=input_q_y).y, output_q_y, atol=out_atol)
881 assert_close(tc, transform.apply_inverse_q(x=output_q_x, y=output_q_y).x, input_q_x, atol=in_atol)
882 assert_close(tc, transform.apply_inverse_q(x=output_q_x, y=output_q_y).y, input_q_y, atol=in_atol)
883 if check_inverted:
884 # Test the inverse transform.
885 check_transform(
886 tc,
887 transform.inverted(),
888 output_xy,
889 input_xy,
890 out_frame,
891 in_frame,
892 check_inverted=False,
893 out_atol=in_atol,
894 in_atol=out_atol,
895 )
898def check_projection[P: Frame](
899 tc: unittest.TestCase,
900 projection: Projection[P],
901 pixel_xy: XY[np.ndarray],
902 sky_coords: SkyCoord,
903 pixel_frame: Frame,
904 *,
905 pixel_atol: float | None = None,
906 sky_atol: u.Quantity | None = None,
907) -> None:
908 """Test a `.Projection` instance against known arrays of pixel and sky
909 coordinates.
911 Parameters
912 ----------
913 tc
914 Test case object with assert methods to use.
915 projection
916 Projection to test.
917 pixel_xy
918 Arrays of pixel coordinates.
919 sky_coords
920 Corresponding sky coordinates.
921 pixel_frame
922 Expected pixel frame.
923 pixel_atol
924 Expected absolute precision of pixel points.
925 sky_atol
926 Expected absolute precision of sky coordinates.
927 """
928 tc.assertEqual(projection.pixel_frame, pixel_frame)
929 tc.assertEqual(projection.sky_frame, SkyFrame.ICRS)
930 sky_atol_v = sky_atol.to_value(SkyFrame.ICRS.unit) if sky_atol is not None else None
931 pixel_atol_q = pixel_atol * u.pix if pixel_atol is not None else None
932 # Test array interfaces.
933 test_pixel_xy = cast(XY[np.ndarray], projection.sky_to_pixel(sky_coords))
934 assert_close(tc, test_pixel_xy.x, pixel_xy.x, atol=pixel_atol)
935 assert_close(tc, test_pixel_xy.y, pixel_xy.y, atol=pixel_atol)
936 test_sky_astropy = projection.pixel_to_sky(x=pixel_xy.x, y=pixel_xy.y)
937 assert_close(tc, test_sky_astropy.ra, sky_coords.ra, atol=sky_atol_v)
938 assert_close(tc, test_sky_astropy.dec, sky_coords.dec, atol=sky_atol_v)
939 # Test scalar interfaces.
940 for pixel_x, pixel_y, sky_single in zip(pixel_xy.x, pixel_xy.y, sky_coords):
941 assert_close(tc, projection.sky_to_pixel(sky_single).x, pixel_x, atol=pixel_atol)
942 assert_close(tc, projection.sky_to_pixel(sky_single).y, pixel_y, atol=pixel_atol)
943 assert_close(tc, projection.pixel_to_sky(x=pixel_x, y=pixel_y).ra, sky_single.ra, atol=sky_atol_v)
944 assert_close(tc, projection.pixel_to_sky(x=pixel_x, y=pixel_y).dec, sky_single.dec, atol=sky_atol_v)
945 # Test the underlying Transform object.
946 sky_xy = XY(x=sky_coords.ra.to_value(u.rad), y=sky_coords.dec.to_value(u.rad))
947 check_transform(
948 tc,
949 projection.pixel_to_sky_transform,
950 pixel_xy,
951 sky_xy,
952 pixel_frame,
953 SkyFrame.ICRS,
954 check_inverted=False,
955 in_atol=pixel_atol_q,
956 out_atol=sky_atol,
957 )
958 check_transform(
959 tc,
960 projection.sky_to_pixel_transform,
961 sky_xy,
962 pixel_xy,
963 SkyFrame.ICRS,
964 pixel_frame,
965 check_inverted=False,
966 in_atol=sky_atol,
967 out_atol=pixel_atol_q,
968 )
969 # Test the Astropy interface adapter.
970 check_astropy_wcs_interface(
971 tc, projection.as_astropy(), pixel_xy, sky_coords, pixel_atol=pixel_atol, sky_atol=sky_atol
972 )
975def assert_projections_equal(
976 tc: unittest.TestCase,
977 a: Projection[Any] | None,
978 b: Projection[Any] | None,
979 expect_identity: bool | None = None,
980) -> None:
981 """Test that two `.Projection` instances are equivalent."""
982 if a is None and b is None:
983 return
984 assert a is not None and b is not None
985 match expect_identity:
986 case True:
987 tc.assertIs(a, b)
988 return
989 case False:
990 tc.assertIsNot(a, b)
991 case None if a is b:
992 return
993 tc.assertEqual(a.pixel_frame, b.pixel_frame)
994 tc.assertEqual(a.show(simplified=True), b.show(simplified=True))
995 assert_projections_equal(
996 tc, a.fits_approximation, cast(Projection[Any], b.fits_approximation), expect_identity=False
997 )
1000def check_astropy_wcs_interface(
1001 tc: unittest.TestCase,
1002 wcs: astropy.wcs.wcsapi.BaseHighLevelWCS,
1003 pixel_xy: XY[np.ndarray],
1004 sky_coords: SkyCoord,
1005 *,
1006 pixel_atol: float | None = None,
1007 sky_atol: u.Quantity | None = None,
1008) -> None:
1009 """Test an Astropy WCS instance against known arrays of pixel and
1010 sky coordinates.
1012 Parameters
1013 ----------
1014 tc
1015 Test case object with assert methods to use.
1016 wcs
1017 Astropy WCS object to test.
1018 pixel_xy
1019 Arrays of pixel coordinates.
1020 sky_coords
1021 Corresponding sky coordinates.
1022 pixel_atol
1023 Expected absolute precision of pixel points.
1024 sky_atol
1025 Expected absolute precision of sky coordinates.
1026 """
1027 test_x, test_y = wcs.world_to_pixel(sky_coords)
1028 assert_close(tc, test_x, pixel_xy.x, atol=pixel_atol)
1029 assert_close(tc, test_y, pixel_xy.y, atol=pixel_atol)
1030 test_sky_coords = wcs.pixel_to_world(pixel_xy.x, pixel_xy.y)
1031 assert_close(tc, test_sky_coords.ra, sky_coords.ra, atol=sky_atol)
1032 assert_close(tc, test_sky_coords.dec, sky_coords.dec, atol=sky_atol)
1035def legacy_points_to_xy_array(legacy_points: list[Any]) -> XY[np.ndarray]:
1036 """Convert a list of ``lsst.geom.Point2D`` objects to an `.XY` array."""
1037 return XY(x=np.array([p.x for p in legacy_points]), y=np.array([p.y for p in legacy_points]))
1040def legacy_coords_to_astropy(legacy_coords: list[Any]) -> SkyCoord:
1041 """Convert a list of ``lsst.geom.SpherePoint`` objects to an Astropy
1042 coordinate object.
1043 """
1044 return SkyCoord(
1045 ra=np.array([p.getRa().asRadians() for p in legacy_coords]) * u.rad,
1046 dec=np.array([p.getDec().asRadians() for p in legacy_coords]) * u.rad,
1047 )
1050def arrays_to_legacy_points(x: np.ndarray, y: np.ndarray) -> list[Any]:
1051 """Convert arrays of ``x`` and ``y`` to a list of ``lsst.geom.Point2D``."""
1052 from lsst.geom import Point2D
1054 return [Point2D(x=xv, y=yv) for xv, yv in zip(x, y)]
1057def compare_amplifier_to_legacy(
1058 tc: unittest.TestCase,
1059 amplifier: Amplifier,
1060 legacy_amplifier: Any,
1061 *,
1062 is_raw_assembled: bool,
1063 expect_nominal_calibrations: bool = True,
1064) -> None:
1065 """Compare an `~.cameras.Amplifier` to a legacy
1066 `lsst.afw.cameraGeom.Amplifier`.
1067 """
1068 tc.assertEqual(legacy_amplifier.getName(), amplifier.name)
1069 tc.assertEqual(Box.from_legacy(legacy_amplifier.getBBox()), amplifier.bbox)
1070 if is_raw_assembled:
1071 raw_geom = amplifier.assembled_raw_geometry
1072 else:
1073 raw_geom = amplifier.unassembled_raw_geometry
1074 assert raw_geom is not None
1075 tc.assertEqual(ReadoutCorner.from_legacy(legacy_amplifier.getReadoutCorner()), raw_geom.readout_corner)
1076 tc.assertEqual(Box.from_legacy(legacy_amplifier.getRawBBox()), raw_geom.bbox)
1077 tc.assertEqual(Box.from_legacy(legacy_amplifier.getRawDataBBox()), raw_geom.data_bbox)
1078 tc.assertEqual(legacy_amplifier.getRawFlipX(), raw_geom.flip_x)
1079 tc.assertEqual(legacy_amplifier.getRawFlipY(), raw_geom.flip_y)
1080 tc.assertEqual(legacy_amplifier.getRawXYOffset().getX(), raw_geom.x_offset)
1081 tc.assertEqual(legacy_amplifier.getRawXYOffset().getY(), raw_geom.y_offset)
1082 tc.assertEqual(
1083 Box.from_legacy(legacy_amplifier.getRawHorizontalOverscanBBox()), raw_geom.horizontal_overscan_bbox
1084 )
1085 tc.assertEqual(
1086 Box.from_legacy(legacy_amplifier.getRawVerticalOverscanBBox()), raw_geom.vertical_overscan_bbox
1087 )
1088 tc.assertEqual(Box.from_legacy(legacy_amplifier.getRawPrescanBBox()), raw_geom.horizontal_prescan_bbox)
1089 if expect_nominal_calibrations:
1090 assert amplifier.nominal_calibrations is not None
1091 assert_equal_allow_nan(tc, legacy_amplifier.getGain(), amplifier.nominal_calibrations.gain)
1092 assert_equal_allow_nan(tc, legacy_amplifier.getReadNoise(), amplifier.nominal_calibrations.read_noise)
1093 assert_equal_allow_nan(
1094 tc, legacy_amplifier.getSaturation(), amplifier.nominal_calibrations.saturation
1095 )
1096 assert_equal_allow_nan(
1097 tc, legacy_amplifier.getSuspectLevel(), amplifier.nominal_calibrations.suspect_level
1098 )
1099 np.testing.assert_array_equal(
1100 legacy_amplifier.getLinearityCoeffs(), amplifier.nominal_calibrations.linearity_coefficients
1101 )
1102 tc.assertEqual(legacy_amplifier.getLinearityType(), amplifier.nominal_calibrations.linearity_type)
1105def compare_detector_to_legacy(
1106 tc: unittest.TestCase,
1107 detector: Detector,
1108 legacy_detector: Any,
1109 *,
1110 is_raw_assembled: bool,
1111 expect_nominal_calibrations: bool = True,
1112) -> None:
1113 """Compare a `~.cameras.Detector` to a `lsst.afw.cameraGeom.Detector`."""
1114 from lsst.afw.cameraGeom import FIELD_ANGLE, FOCAL_PLANE, PIXELS
1116 tc.assertEqual(legacy_detector.getName(), detector.name)
1117 tc.assertEqual(legacy_detector.getId(), detector.id)
1118 tc.assertEqual(DetectorType.from_legacy(legacy_detector.getType()), detector.type)
1119 tc.assertEqual(Box.from_legacy(legacy_detector.getBBox()), detector.bbox)
1120 tc.assertEqual(legacy_detector.getSerial(), detector.serial)
1121 legacy_orientation = legacy_detector.getOrientation()
1122 tc.assertEqual(legacy_orientation.getFpPosition3().getX(), detector.orientation.focal_plane_x)
1123 tc.assertEqual(legacy_orientation.getFpPosition3().getY(), detector.orientation.focal_plane_y)
1124 tc.assertEqual(legacy_orientation.getFpPosition3().getZ(), detector.orientation.focal_plane_z)
1125 tc.assertEqual(legacy_orientation.getReferencePoint().getX(), detector.orientation.pixel_reference_x)
1126 tc.assertEqual(legacy_orientation.getReferencePoint().getY(), detector.orientation.pixel_reference_y)
1127 tc.assertEqual(legacy_orientation.getYaw().asRadians(), detector.orientation.yaw.to_value(u.rad))
1128 tc.assertEqual(legacy_orientation.getPitch().asRadians(), detector.orientation.pitch.to_value(u.rad))
1129 tc.assertEqual(legacy_orientation.getRoll().asRadians(), detector.orientation.roll.to_value(u.rad))
1130 tc.assertEqual(legacy_detector.getPixelSize().getX(), detector.pixel_size)
1131 tc.assertEqual(legacy_detector.getPhysicalType(), detector.physical_type)
1132 for amplifier, legacy_amplifier in zip(detector.amplifiers, legacy_detector.getAmplifiers(), strict=True):
1133 compare_amplifier_to_legacy(
1134 tc,
1135 amplifier,
1136 legacy_amplifier,
1137 is_raw_assembled=is_raw_assembled,
1138 expect_nominal_calibrations=expect_nominal_calibrations,
1139 )
1140 pixel_xy = detector.bbox.meshgrid(n=3).map(lambda z: z.ravel().astype(np.float64))
1141 pixel_legacy_points = arrays_to_legacy_points(y=pixel_xy.y, x=pixel_xy.x)
1142 fp_legacy_points = legacy_detector.transform(pixel_legacy_points, PIXELS, FOCAL_PLANE)
1143 check_transform(
1144 tc,
1145 detector.to_focal_plane,
1146 pixel_xy,
1147 legacy_points_to_xy_array(fp_legacy_points),
1148 detector.frame,
1149 detector.to_focal_plane.out_frame,
1150 in_atol=1e-9 * u.pix,
1151 out_atol=1e-7 * detector.to_focal_plane.out_frame.unit,
1152 )
1153 fa_legacy_points = legacy_detector.transform(pixel_legacy_points, PIXELS, FIELD_ANGLE)
1154 check_transform(
1155 tc,
1156 detector.to_field_angle,
1157 pixel_xy,
1158 legacy_points_to_xy_array(fa_legacy_points),
1159 detector.frame,
1160 detector.to_field_angle.out_frame,
1161 in_atol=1e-9 * u.pix,
1162 out_atol=1e-7 * u.arcsec,
1163 )