Coverage for python / lsst / images / tests / _checks.py: 10%
379 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-20 01:32 -0700
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-20 01:32 -0700
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 projection := alternates.get("projection"):
347 compare_projection_to_legacy_wcs(
348 tc,
349 projection,
350 legacy_exposure.getWcs(),
351 DetectorFrame(instrument=instrument, visit=visit, detector=detector, bbox=detector_bbox),
352 visit_image.bbox,
353 )
354 if psf := alternates.get("psf"):
355 compare_psf_to_legacy(tc, psf, legacy_exposure.getPsf())
356 if summary_stats := alternates.get("summary_stats"):
357 compare_observation_summary_stats_to_legacy(
358 tc, summary_stats, legacy_exposure.info.getSummaryStats()
359 )
360 if detector_obj := alternates.get("detector"):
361 compare_detector_to_legacy(tc, detector_obj, legacy_exposure.getDetector(), is_raw_assembled=True)
362 if obs_info := alternates.get("obs_info"):
363 visitInfo = legacy_exposure.visitInfo
364 tc.assertEqual(obs_info.instrument, visitInfo.getInstrumentLabel())
365 if aperture_corrections := alternates.get("aperture_corrections"):
366 compare_aperture_corrections_to_legacy(
367 tc, aperture_corrections, legacy_exposure.info.getApCorrMap(), tiny_bbox
368 )
369 if (photometric_scaling := alternates.get("photometic_scaling", ...)) is not ...:
370 compare_photo_calib_to_legacy(
371 tc,
372 photometric_scaling,
373 legacy_exposure.info.getPhotoCalib(),
374 applied_legacy_photo_calib=applied_legacy_photo_calib,
375 subimage_bbox=tiny_bbox,
376 )
379def compare_photo_calib_to_legacy(
380 tc: unittest.TestCase,
381 photometric_scaling: BaseField | None,
382 legacy_photo_calib: LegacyPhotoCalib,
383 *,
384 applied_legacy_photo_calib: LegacyPhotoCalib | None = None,
385 subimage_bbox: Box,
386) -> None:
387 if legacy_photo_calib._isConstant:
388 if legacy_photo_calib.getCalibrationMean() == 1.0:
389 if applied_legacy_photo_calib is None:
390 tc.assertIsNone(photometric_scaling)
391 return
392 else:
393 legacy_photo_calib = applied_legacy_photo_calib
394 if legacy_photo_calib._isConstant:
395 assert isinstance(photometric_scaling, ChebyshevField)
396 assert_close(
397 tc, photometric_scaling.coefficients, np.array([[legacy_photo_calib.getCalibrationMean()]])
398 )
399 else:
400 assert photometric_scaling is not None
401 compare_field_to_legacy(
402 tc,
403 photometric_scaling / legacy_photo_calib.getCalibrationMean(),
404 legacy_photo_calib.computeScaledCalibration(),
405 subimage_bbox,
406 )
409def compare_cell_coadd_to_legacy(
410 tc: unittest.TestCase,
411 cell_coadd: CellCoadd,
412 legacy_cell_coadd: MultipleCellCoadd,
413 *,
414 tract_bbox: Box,
415 plane_map: Mapping[str, MaskPlane] | None = None,
416 alternates: Mapping[str, Any] | None = None,
417 psf_points: XY[np.ndarray] | YX[np.ndarray] | None = None,
418) -> None:
419 """Compare a `.cells.CellCoadd` object to a legacy
420 `lsst.cell_coadds.MultipleCellCoadd` object.
422 Parameters
423 ----------
424 tc
425 Test case to use for asserts.
426 cell_coadd
427 New coadd to test.
428 legacy_cell_coadd
429 Legacy coadd to test against.
430 tract_bbox
431 Bounding box of the full tract.
432 psf_points
433 Points to use to compare the PSFs.
434 plane_map
435 Mapping between new and legacy mask planes.
436 alternates
437 A mapping of other versions of one or more (new) components to also
438 check against the legacy versions of those components.
439 """
440 legacy_stitched = legacy_cell_coadd.stitch(cell_coadd.bbox.to_legacy())
441 compare_image_to_legacy(tc, cell_coadd.image, legacy_stitched.image, expect_view=False)
442 compare_mask_to_legacy(tc, cell_coadd.mask, legacy_stitched.mask, plane_map=plane_map)
443 compare_image_to_legacy(tc, cell_coadd.variance, legacy_stitched.variance, expect_view=False)
444 if legacy_stitched.mask_fractions is not None:
445 compare_image_to_legacy(
446 tc, cell_coadd.mask_fractions["rejected"], legacy_stitched.mask_fractions, expect_view=False
447 )
448 for n in range(legacy_stitched.n_noise_realizations):
449 compare_image_to_legacy(
450 tc, cell_coadd.noise_realizations[n], legacy_stitched.noise_realizations[n], expect_view=False
451 )
452 tc.assertEqual(cell_coadd.skymap, legacy_stitched.identifiers.skymap)
453 tc.assertEqual(cell_coadd.tract, legacy_stitched.identifiers.tract)
454 tc.assertEqual(cell_coadd.patch.index.x, legacy_stitched.identifiers.patch.x)
455 tc.assertEqual(cell_coadd.patch.index.y, legacy_stitched.identifiers.patch.y)
456 tc.assertEqual(cell_coadd.band, legacy_stitched.identifiers.band)
457 tc.assertTrue(tract_bbox.contains(cell_coadd.patch.outer_bbox))
458 tc.assertTrue(cell_coadd.patch.outer_bbox.contains(cell_coadd.patch.inner_bbox))
459 tc.assertTrue(cell_coadd.patch.outer_bbox.contains(cell_coadd.bbox))
460 tc.assertEqual(cell_coadd.unit, u.Unit(legacy_cell_coadd.common.units.value))
461 tc.assertTrue(cell_coadd.bounds.bbox.contains(cell_coadd.bbox))
462 tc.assertTrue(cell_coadd.grid.bbox.contains(cell_coadd.bbox))
463 compare_projection_to_legacy_wcs(
464 tc,
465 cell_coadd.projection,
466 legacy_cell_coadd.wcs,
467 TractFrame(
468 skymap=legacy_cell_coadd.identifiers.skymap,
469 tract=legacy_cell_coadd.identifiers.tract,
470 bbox=tract_bbox,
471 ),
472 cell_coadd.bbox,
473 is_fits=True,
474 )
475 tc.assertIs(cell_coadd.projection, cell_coadd.mask.projection)
476 tc.assertIs(cell_coadd.projection, cell_coadd.variance.projection)
477 compare_psf_to_legacy(
478 tc, cell_coadd.psf, legacy_stitched.psf, expect_legacy_raise_on_out_of_bounds=True, points=psf_points
479 )
480 compare_cell_coadd_provenance_to_legacy(tc, cell_coadd.provenance, legacy_cell_coadd)
481 if alternates:
482 if projection := alternates.get("projection"):
483 compare_projection_to_legacy_wcs(
484 tc,
485 projection,
486 legacy_stitched.wcs,
487 TractFrame(
488 skymap=legacy_cell_coadd.identifiers.skymap,
489 tract=legacy_cell_coadd.identifiers.tract,
490 bbox=tract_bbox,
491 ),
492 cell_coadd.bbox,
493 is_fits=True,
494 )
495 if psf := alternates.get("psf"):
496 compare_psf_to_legacy(tc, psf, legacy_stitched.psf, points=psf_points)
497 if provenance := alternates.get("provenance"):
498 compare_cell_coadd_provenance_to_legacy(tc, provenance, legacy_cell_coadd)
501def compare_cell_coadd_provenance_to_legacy(
502 tc: unittest.TestCase, provenance: CoaddProvenance, legacy_cell_coadd: MultipleCellCoadd
503) -> None:
504 """Compare a `.cells.CoaddProvenance` object to a legacy
505 `lsst.cell_coadds.MultipleCellCoadd` object.
507 Parameters
508 ----------
509 tc
510 Test case to use for asserts.
511 provenance
512 New provenance object to test.
513 legacy_cell_coadd
514 Legacy coadd to test against.
515 """
516 from lsst.cell_coadds import ObservationIdentifiers
518 for legacy_cell in legacy_cell_coadd.cells.values():
519 cell_index = CellIJ.from_legacy(legacy_cell.identifiers.cell)
520 prov = provenance[cell_index]
521 legacy_table = astropy.table.Table(
522 rows=[
523 [
524 ids.instrument,
525 ids.visit,
526 ids.detector,
527 ids.day_obs,
528 ids.physical_filter,
529 legacy_input.overlaps_center,
530 legacy_input.overlap_fraction,
531 legacy_input.weight,
532 legacy_input.psf_shape.getIxx(),
533 legacy_input.psf_shape.getIyy(),
534 legacy_input.psf_shape.getIxy(),
535 legacy_input.psf_shape_flag,
536 ]
537 for ids, legacy_input in legacy_cell.inputs.items()
538 ],
539 dtype=[
540 np.object_,
541 np.uint64,
542 np.uint16,
543 np.uint32,
544 np.object_,
545 np.bool_,
546 np.float64,
547 np.float64,
548 np.float64,
549 np.float64,
550 np.float64,
551 np.bool_,
552 ],
553 names=[
554 "instrument",
555 "visit",
556 "detector",
557 "day_obs",
558 "physical_filter",
559 "overlaps_center",
560 "overlap_fraction",
561 "weight",
562 "psf_shape_xx",
563 "psf_shape_yy",
564 "psf_shape_xy",
565 "psf_shape_flag",
566 ],
567 )
568 # For a single cell all 'inputs' are also 'contributions'.
569 tc.assertEqual(len(legacy_cell.inputs), len(prov.inputs))
570 tc.assertEqual(len(legacy_cell.inputs), len(prov.contributions))
571 prov.inputs.sort(["instrument", "visit", "detector"])
572 prov.contributions.sort(["instrument", "visit", "detector"])
573 legacy_table.sort(["instrument", "visit", "detector"])
574 np.testing.assert_array_equal(prov.inputs["instrument"], prov.contributions["instrument"])
575 np.testing.assert_array_equal(prov.inputs["visit"], prov.contributions["visit"])
576 np.testing.assert_array_equal(prov.inputs["detector"], prov.contributions["detector"])
577 np.testing.assert_array_equal(prov.inputs["instrument"], legacy_table["instrument"])
578 np.testing.assert_array_equal(prov.inputs["visit"], legacy_table["visit"])
579 np.testing.assert_array_equal(prov.inputs["detector"], legacy_table["detector"])
580 np.testing.assert_array_equal(prov.inputs["physical_filter"], legacy_table["physical_filter"])
581 np.testing.assert_array_equal(prov.inputs["day_obs"], legacy_table["day_obs"])
582 np.testing.assert_array_equal(prov.contributions["overlaps_center"], legacy_table["overlaps_center"])
583 np.testing.assert_array_equal(
584 prov.contributions["overlap_fraction"], legacy_table["overlap_fraction"]
585 )
586 np.testing.assert_array_equal(prov.contributions["weight"], legacy_table["weight"])
587 np.testing.assert_array_equal(prov.contributions["psf_shape_xx"], legacy_table["psf_shape_xx"])
588 np.testing.assert_array_equal(prov.contributions["psf_shape_yy"], legacy_table["psf_shape_yy"])
589 np.testing.assert_array_equal(prov.contributions["psf_shape_xy"], legacy_table["psf_shape_xy"])
590 np.testing.assert_array_equal(prov.contributions["psf_shape_flag"], legacy_table["psf_shape_flag"])
591 for row in prov.inputs:
592 polygon_key = ObservationIdentifiers(**{k: row[k] for k in row.keys() if k != "polygon"})
593 legacy_polygon = legacy_cell_coadd.common.visit_polygons[polygon_key]
594 tc.assertEqual(legacy_polygon, row["polygon"].to_legacy())
597def compare_psf_to_legacy(
598 tc: unittest.TestCase,
599 psf: PointSpreadFunction,
600 legacy_psf: Any,
601 points: YX[np.ndarray] | XY[np.ndarray] | None = None,
602 expect_legacy_raise_on_out_of_bounds: bool = False,
603) -> int:
604 """Compare a PSF model object to its legacy interface.
606 Parameters
607 ----------
608 tc
609 Test case object with assert methods to use.
610 psf
611 Point-spread function to test.
612 legacy_psf
613 Legacy `lsst.afw.detection.Psf` instance to compare with.
614 points
615 Points to evaluate the PSFs at. If not provided, the intersection of
616 the PSF bounding boxes are used to generate points on a grid.
617 expect_legacy_raise_on_out_of_bounds
618 If `True`, expect ``legacy_psf`` to raise
619 `lsst.afw.detection.InvalidPsfError` when evaluated at a position
620 considered out-of-bounds by ``psf``.
622 Returns
623 -------
624 `int`
625 The number of points actually tested.
626 """
627 from lsst.afw.detection import InvalidPsfError
629 if points is None:
630 points = psf.bounds.bbox.meshgrid(n=3).map(np.ravel)
631 legacy_points = arrays_to_legacy_points(points.x, points.y)
632 n_points_tested: int = 0
633 for p in legacy_points:
634 if not psf.bounds.contains(x=p.x, y=p.y):
635 if expect_legacy_raise_on_out_of_bounds:
636 with tc.assertRaises(InvalidPsfError):
637 legacy_psf.computeKernelImage(p)
638 continue
639 tc.assertEqual(psf.kernel_bbox, Box.from_legacy(legacy_psf.computeKernelBBox(p)))
640 tc.assertEqual(
641 psf.compute_kernel_image(x=p.x, y=p.y), Image.from_legacy(legacy_psf.computeKernelImage(p))
642 )
643 tc.assertEqual(
644 psf.compute_stellar_bbox(x=p.x, y=p.y), Box.from_legacy(legacy_psf.computeImageBBox(p))
645 )
646 tc.assertEqual(psf.compute_stellar_image(x=p.x, y=p.y), Image.from_legacy(legacy_psf.computeImage(p)))
647 n_points_tested += 1
648 return n_points_tested
651def compare_field_to_legacy(
652 tc: unittest.TestCase,
653 field: BaseField,
654 legacy_field: Any,
655 subimage_bbox: Box,
656) -> None:
657 """Test a Field object by comparing it to an equivalent
658 `lsst.afw.math.BoundedField`.
660 Parameters
661 ----------
662 tc
663 Test case object with assert methods to use.
664 field
665 Field to test.
666 legacy_field : ``lsst.afw.math.BoundedField``
667 Equivalent legacy bounded field.
668 subimage_bbox
669 Bounding box for full-image tests.
670 """
671 tc.assertEqual(field.bounds.bbox, Box.from_legacy(legacy_field.getBBox()))
672 # Pixel coordinates to test the numpy array interface with.
673 pixel_xy = field.bounds.bbox.meshgrid(n=5).map(np.ravel)
674 assert_close(tc, field(x=pixel_xy.x, y=pixel_xy.y), legacy_field.evaluate(pixel_xy.x, pixel_xy.y))
675 legacy_image_1 = Image(0, bbox=subimage_bbox, dtype=np.float64).to_legacy()
676 legacy_field.addToImage(legacy_image_1, overlapOnly=True)
677 assert_images_equal(
678 tc, field.render(subimage_bbox), Image.from_legacy(legacy_image_1, unit=field.unit), rtol=1e-13
679 )
682def compare_aperture_corrections_to_legacy(
683 tc: unittest.TestCase,
684 aperture_corrections: ApertureCorrectionMap,
685 legacy_ap_corr_map: Any,
686 subimage_bbox: Box,
687) -> None:
688 """Test an aperture correction `dict` by comparing it to an equivalent
689 `lsst.afw.image.ApCorrMap`.
691 Parameters
692 ----------
693 tc
694 Test case object with assert methods to use.
695 aperture_corrections
696 Dictionary to test.
697 legacy_ap_corr_map : ``lsst.afw.image.ApCorrMap``
698 Equivalent legacy aperture correction map.
699 subimage_bbox
700 Bounding box for full-image tests.
701 """
702 tc.assertEqual(aperture_corrections.keys(), set(legacy_ap_corr_map.keys()))
703 for name, field in aperture_corrections.items():
704 compare_field_to_legacy(tc, field, legacy_ap_corr_map[name], subimage_bbox)
707def compare_observation_summary_stats_to_legacy(
708 tc: unittest.TestCase,
709 summary_stats: ObservationSummaryStats,
710 legacy_summary_stats: Any,
711) -> None:
712 """Test an ObservationSummaryStats object by comparing it to an equivalent
713 `lsst.afw.image.ExposureSummaryStats`.
715 Parameters
716 ----------
717 tc
718 Test case object with assert methods to use.
719 summary_stats
720 Struct to test.
721 legacy : ``lsst.afw.image.ExposureSummaryStats``
722 Equivalent legacy struct.
723 """
724 for field in dataclasses.fields(legacy_summary_stats):
725 a = getattr(legacy_summary_stats, field.name)
726 b = getattr(summary_stats, field.name)
727 if isinstance(b, tuple):
728 for ai, bi in zip(a, b):
729 tc.assertTrue(ai == bi or (math.isnan(ai) and math.isnan(bi)), f"{field.name}: {a} != {b}")
730 else:
731 tc.assertTrue(a == b or (math.isnan(a) and math.isnan(b)), f"{field.name}: {a} != {b}")
734def compare_projection_to_legacy_wcs[F: Frame](
735 tc: unittest.TestCase,
736 projection: Projection[F],
737 legacy_wcs: Any,
738 pixel_frame: F,
739 subimage_bbox: Box,
740 is_fits: bool = False,
741) -> None:
742 """Test a Projection object by comparing it to an equivalent
743 `lsst.afw.geom.SkyWcs`.
745 Parameters
746 ----------
747 tc
748 Test case object with assert methods to use.
749 projection
750 Projection to test.
751 legacy_wcs : ``lsst.afw.geom.SkyWcs``
752 Equivalent legacy WCS.
753 pixel_frame
754 Expected pixel frame for the projection.
755 subimage_bbox
756 Bounding box of points to generate for tests.
757 is_fits
758 Whether this projection is expected to be exactly representable as a
759 FIT WCS. If `False` it is assumed to have a FITS approximation
760 attached instead.
761 """
762 # Pixel coordinates to test on over the subimage region of interest:
763 pixel_xy = subimage_bbox.meshgrid(step=50).map(np.ravel)
764 # Array indices of those pixel values (subtract off bbox starts):
765 subimage_array_xy = XY(x=pixel_xy.x - subimage_bbox.x.start, y=pixel_xy.y - subimage_bbox.y.start)
766 sky_coords = legacy_coords_to_astropy(
767 legacy_wcs.pixelToSky(arrays_to_legacy_points(pixel_xy.x, pixel_xy.y))
768 )
769 # Test transforming with the Projection itself, which also tests its
770 # nested Transform and an Astropy High-Level WCS view with no origin
771 # change.
772 check_projection(tc, projection, pixel_xy, sky_coords, pixel_frame)
773 # Also test the Astropy High-Level WCS view with an origin change to
774 # array indices.
775 check_astropy_wcs_interface(
776 tc, projection.as_astropy(subimage_bbox), subimage_array_xy, sky_coords, pixel_atol=1e-5
777 )
778 if is_fits:
779 fits_wcs = projection.as_fits_wcs(subimage_bbox, allow_approximation=True)
780 assert fits_wcs is not None
781 check_astropy_wcs_interface(tc, fits_wcs, subimage_array_xy, sky_coords, pixel_atol=1e-5)
782 # Use that FITS approximation to check that we can make a
783 # Projection from a FITS WCS, too.
784 fits_projection = Projection.from_fits_wcs(fits_wcs, pixel_frame)
785 check_projection(
786 tc,
787 fits_projection,
788 subimage_array_xy,
789 sky_coords,
790 pixel_frame,
791 pixel_atol=1e-5,
792 )
793 # We want Projections we create from a FITS WCS to be backed by an
794 # AST FrameSet so we can convert them into legacy
795 # `lsst.afw.geom.SkyWcs` objects if desired.
796 tc.assertIn("Begin FrameSet", fits_projection.show())
797 else:
798 tc.assertIsNone(projection.as_fits_wcs(subimage_bbox, allow_approximation=False))
799 # The legacy SkyWcs should instead have a FITS approximation
800 # attached; run the same tests on that.
801 assert projection.fits_approximation is not None
802 compare_projection_to_legacy_wcs(
803 tc,
804 projection.fits_approximation,
805 legacy_wcs.getFitsApproximation(),
806 pixel_frame,
807 subimage_bbox,
808 is_fits=True,
809 )
812def check_transform[I: Frame, O: Frame](
813 tc: unittest.TestCase,
814 transform: Transform[I, O],
815 input_xy: XY[np.ndarray],
816 output_xy: XY[np.ndarray],
817 in_frame: Frame,
818 out_frame: Frame,
819 *,
820 check_inverted: bool = True,
821 in_atol: u.Quantity | None = None,
822 out_atol: u.Quantity | None = None,
823) -> None:
824 """Test Transform against known arrays of input and output points.
826 Parameters
827 ----------
828 tc
829 Test case object with assert methods to use.
830 transform
831 Transform to test.
832 input_xy
833 Arrays of input points.
834 output_xy
835 Arrays of output points.
836 in_frame
837 Expected input frame.
838 out_frame
839 Expected output frame.
840 check_inverted
841 If `True`, recurse (once) to test the inverse transform.
842 in_atol
843 Expected absolute precision of input points.
844 out_atol
845 Expected absolute precision of output points.
846 """
847 tc.assertEqual(transform.in_frame, in_frame)
848 tc.assertEqual(transform.out_frame, out_frame)
849 in_atol_v = in_atol.to_value(in_frame.unit) if in_atol is not None else None
850 out_atol_v = out_atol.to_value(out_frame.unit) if out_atol is not None else None
851 # Test array interfaces.
852 test_output_xy = transform.apply_forward(x=input_xy.x, y=input_xy.y)
853 assert_close(tc, test_output_xy.x, output_xy.x, atol=out_atol_v)
854 assert_close(tc, test_output_xy.y, output_xy.y, atol=out_atol_v)
855 test_input_xy = transform.apply_inverse(x=output_xy.x, y=output_xy.y)
856 assert_close(tc, test_input_xy.x, input_xy.x, atol=in_atol_v)
857 assert_close(tc, test_input_xy.y, input_xy.y, atol=in_atol_v)
858 # Test scalar interfaces with numpy scalars.
859 for input_x, input_y, output_x, output_y in zip(input_xy.x, input_xy.y, output_xy.x, output_xy.y):
860 assert_close(tc, transform.apply_forward(x=input_x, y=input_y).x, output_x, atol=out_atol_v)
861 assert_close(tc, transform.apply_forward(x=input_x, y=input_y).y, output_y, atol=out_atol_v)
862 assert_close(tc, transform.apply_inverse(x=output_x, y=output_y).x, input_x, atol=in_atol_v)
863 assert_close(tc, transform.apply_inverse(x=output_x, y=output_y).y, input_y, atol=in_atol_v)
864 # Test quantity array interfaces.
865 input_q_xy = XY(x=input_xy.x * transform.in_frame.unit, y=input_xy.y * transform.in_frame.unit)
866 output_q_xy = XY(x=output_xy.x * transform.out_frame.unit, y=output_xy.y * transform.out_frame.unit)
867 test_output_q_xy = transform.apply_forward_q(x=input_q_xy.x, y=input_q_xy.y)
868 assert_close(tc, test_output_q_xy.x, output_q_xy.x, atol=out_atol)
869 assert_close(tc, test_output_q_xy.y, output_q_xy.y, atol=out_atol)
870 test_input_q_xy = transform.apply_inverse_q(x=output_q_xy.x, y=output_q_xy.y)
871 assert_close(tc, test_input_q_xy.x, input_q_xy.x, atol=in_atol)
872 assert_close(tc, test_input_q_xy.y, input_q_xy.y, atol=in_atol)
873 # Test quantity scalar interfaces.
874 for input_q_x, input_q_y, output_q_x, output_q_y in zip(
875 input_q_xy.x, input_q_xy.y, output_q_xy.x, output_q_xy.y
876 ):
877 assert_close(tc, transform.apply_forward_q(x=input_q_x, y=input_q_y).x, output_q_x, atol=out_atol)
878 assert_close(tc, transform.apply_forward_q(x=input_q_x, y=input_q_y).y, output_q_y, atol=out_atol)
879 assert_close(tc, transform.apply_inverse_q(x=output_q_x, y=output_q_y).x, input_q_x, atol=in_atol)
880 assert_close(tc, transform.apply_inverse_q(x=output_q_x, y=output_q_y).y, input_q_y, atol=in_atol)
881 if check_inverted:
882 # Test the inverse transform.
883 check_transform(
884 tc,
885 transform.inverted(),
886 output_xy,
887 input_xy,
888 out_frame,
889 in_frame,
890 check_inverted=False,
891 out_atol=in_atol,
892 in_atol=out_atol,
893 )
896def check_projection[P: Frame](
897 tc: unittest.TestCase,
898 projection: Projection[P],
899 pixel_xy: XY[np.ndarray],
900 sky_coords: SkyCoord,
901 pixel_frame: Frame,
902 *,
903 pixel_atol: float | None = None,
904 sky_atol: u.Quantity | None = None,
905) -> None:
906 """Test a `.Projection` instance against known arrays of pixel and sky
907 coordinates.
909 Parameters
910 ----------
911 tc
912 Test case object with assert methods to use.
913 projection
914 Projection to test.
915 pixel_xy
916 Arrays of pixel coordinates.
917 sky_coords
918 Corresponding sky coordinates.
919 pixel_frame
920 Expected pixel frame.
921 pixel_atol
922 Expected absolute precision of pixel points.
923 sky_atol
924 Expected absolute precision of sky coordinates.
925 """
926 tc.assertEqual(projection.pixel_frame, pixel_frame)
927 tc.assertEqual(projection.sky_frame, SkyFrame.ICRS)
928 sky_atol_v = sky_atol.to_value(SkyFrame.ICRS.unit) if sky_atol is not None else None
929 pixel_atol_q = pixel_atol * u.pix if pixel_atol is not None else None
930 # Test array interfaces.
931 test_pixel_xy = cast(XY[np.ndarray], projection.sky_to_pixel(sky_coords))
932 assert_close(tc, test_pixel_xy.x, pixel_xy.x, atol=pixel_atol)
933 assert_close(tc, test_pixel_xy.y, pixel_xy.y, atol=pixel_atol)
934 test_sky_astropy = projection.pixel_to_sky(x=pixel_xy.x, y=pixel_xy.y)
935 assert_close(tc, test_sky_astropy.ra, sky_coords.ra, atol=sky_atol_v)
936 assert_close(tc, test_sky_astropy.dec, sky_coords.dec, atol=sky_atol_v)
937 # Test scalar interfaces.
938 for pixel_x, pixel_y, sky_single in zip(pixel_xy.x, pixel_xy.y, sky_coords):
939 assert_close(tc, projection.sky_to_pixel(sky_single).x, pixel_x, atol=pixel_atol)
940 assert_close(tc, projection.sky_to_pixel(sky_single).y, pixel_y, atol=pixel_atol)
941 assert_close(tc, projection.pixel_to_sky(x=pixel_x, y=pixel_y).ra, sky_single.ra, atol=sky_atol_v)
942 assert_close(tc, projection.pixel_to_sky(x=pixel_x, y=pixel_y).dec, sky_single.dec, atol=sky_atol_v)
943 # Test the underlying Transform object.
944 sky_xy = XY(x=sky_coords.ra.to_value(u.rad), y=sky_coords.dec.to_value(u.rad))
945 check_transform(
946 tc,
947 projection.pixel_to_sky_transform,
948 pixel_xy,
949 sky_xy,
950 pixel_frame,
951 SkyFrame.ICRS,
952 check_inverted=False,
953 in_atol=pixel_atol_q,
954 out_atol=sky_atol,
955 )
956 check_transform(
957 tc,
958 projection.sky_to_pixel_transform,
959 sky_xy,
960 pixel_xy,
961 SkyFrame.ICRS,
962 pixel_frame,
963 check_inverted=False,
964 in_atol=sky_atol,
965 out_atol=pixel_atol_q,
966 )
967 # Test the Astropy interface adapter.
968 check_astropy_wcs_interface(
969 tc, projection.as_astropy(), pixel_xy, sky_coords, pixel_atol=pixel_atol, sky_atol=sky_atol
970 )
973def assert_projections_equal(
974 tc: unittest.TestCase,
975 a: Projection[Any] | None,
976 b: Projection[Any] | None,
977 expect_identity: bool | None = None,
978) -> None:
979 """Test that two `.Projection` instances are equivalent."""
980 if a is None and b is None:
981 return
982 assert a is not None and b is not None
983 match expect_identity:
984 case True:
985 tc.assertIs(a, b)
986 return
987 case False:
988 tc.assertIsNot(a, b)
989 case None if a is b:
990 return
991 tc.assertEqual(a.pixel_frame, b.pixel_frame)
992 tc.assertEqual(a.show(simplified=True), b.show(simplified=True))
993 assert_projections_equal(
994 tc, a.fits_approximation, cast(Projection[Any], b.fits_approximation), expect_identity=False
995 )
998def check_astropy_wcs_interface(
999 tc: unittest.TestCase,
1000 wcs: astropy.wcs.wcsapi.BaseHighLevelWCS,
1001 pixel_xy: XY[np.ndarray],
1002 sky_coords: SkyCoord,
1003 *,
1004 pixel_atol: float | None = None,
1005 sky_atol: u.Quantity | None = None,
1006) -> None:
1007 """Test an Astropy WCS instance against known arrays of pixel and
1008 sky coordinates.
1010 Parameters
1011 ----------
1012 tc
1013 Test case object with assert methods to use.
1014 wcs
1015 Astropy WCS object to test.
1016 pixel_xy
1017 Arrays of pixel coordinates.
1018 sky_coords
1019 Corresponding sky coordinates.
1020 pixel_atol
1021 Expected absolute precision of pixel points.
1022 sky_atol
1023 Expected absolute precision of sky coordinates.
1024 """
1025 test_x, test_y = wcs.world_to_pixel(sky_coords)
1026 assert_close(tc, test_x, pixel_xy.x, atol=pixel_atol)
1027 assert_close(tc, test_y, pixel_xy.y, atol=pixel_atol)
1028 test_sky_coords = wcs.pixel_to_world(pixel_xy.x, pixel_xy.y)
1029 assert_close(tc, test_sky_coords.ra, sky_coords.ra, atol=sky_atol)
1030 assert_close(tc, test_sky_coords.dec, sky_coords.dec, atol=sky_atol)
1033def legacy_points_to_xy_array(legacy_points: list[Any]) -> XY[np.ndarray]:
1034 """Convert a list of ``lsst.geom.Point2D`` objects to an `.XY` array."""
1035 return XY(x=np.array([p.x for p in legacy_points]), y=np.array([p.y for p in legacy_points]))
1038def legacy_coords_to_astropy(legacy_coords: list[Any]) -> SkyCoord:
1039 """Convert a list of ``lsst.geom.SpherePoint`` objects to an Astropy
1040 coordinate object.
1041 """
1042 return SkyCoord(
1043 ra=np.array([p.getRa().asRadians() for p in legacy_coords]) * u.rad,
1044 dec=np.array([p.getDec().asRadians() for p in legacy_coords]) * u.rad,
1045 )
1048def arrays_to_legacy_points(x: np.ndarray, y: np.ndarray) -> list[Any]:
1049 """Convert arrays of ``x`` and ``y`` to a list of ``lsst.geom.Point2D``."""
1050 from lsst.geom import Point2D
1052 return [Point2D(x=xv, y=yv) for xv, yv in zip(x, y)]
1055def compare_amplifier_to_legacy(
1056 tc: unittest.TestCase,
1057 amplifier: Amplifier,
1058 legacy_amplifier: Any,
1059 *,
1060 is_raw_assembled: bool,
1061 expect_nominal_calibrations: bool = True,
1062) -> None:
1063 """Compare an `~.cameras.Amplifier` to a legacy
1064 `lsst.afw.cameraGeom.Amplifier`.
1065 """
1066 tc.assertEqual(legacy_amplifier.getName(), amplifier.name)
1067 tc.assertEqual(Box.from_legacy(legacy_amplifier.getBBox()), amplifier.bbox)
1068 if is_raw_assembled:
1069 raw_geom = amplifier.assembled_raw_geometry
1070 else:
1071 raw_geom = amplifier.unassembled_raw_geometry
1072 assert raw_geom is not None
1073 tc.assertEqual(ReadoutCorner.from_legacy(legacy_amplifier.getReadoutCorner()), raw_geom.readout_corner)
1074 tc.assertEqual(Box.from_legacy(legacy_amplifier.getRawBBox()), raw_geom.bbox)
1075 tc.assertEqual(Box.from_legacy(legacy_amplifier.getRawDataBBox()), raw_geom.data_bbox)
1076 tc.assertEqual(legacy_amplifier.getRawFlipX(), raw_geom.flip_x)
1077 tc.assertEqual(legacy_amplifier.getRawFlipY(), raw_geom.flip_y)
1078 tc.assertEqual(legacy_amplifier.getRawXYOffset().getX(), raw_geom.x_offset)
1079 tc.assertEqual(legacy_amplifier.getRawXYOffset().getY(), raw_geom.y_offset)
1080 tc.assertEqual(
1081 Box.from_legacy(legacy_amplifier.getRawHorizontalOverscanBBox()), raw_geom.horizontal_overscan_bbox
1082 )
1083 tc.assertEqual(
1084 Box.from_legacy(legacy_amplifier.getRawVerticalOverscanBBox()), raw_geom.vertical_overscan_bbox
1085 )
1086 tc.assertEqual(Box.from_legacy(legacy_amplifier.getRawPrescanBBox()), raw_geom.horizontal_prescan_bbox)
1087 if expect_nominal_calibrations:
1088 assert amplifier.nominal_calibrations is not None
1089 assert_equal_allow_nan(tc, legacy_amplifier.getGain(), amplifier.nominal_calibrations.gain)
1090 assert_equal_allow_nan(tc, legacy_amplifier.getReadNoise(), amplifier.nominal_calibrations.read_noise)
1091 assert_equal_allow_nan(
1092 tc, legacy_amplifier.getSaturation(), amplifier.nominal_calibrations.saturation
1093 )
1094 assert_equal_allow_nan(
1095 tc, legacy_amplifier.getSuspectLevel(), amplifier.nominal_calibrations.suspect_level
1096 )
1097 np.testing.assert_array_equal(
1098 legacy_amplifier.getLinearityCoeffs(), amplifier.nominal_calibrations.linearity_coefficients
1099 )
1100 tc.assertEqual(legacy_amplifier.getLinearityType(), amplifier.nominal_calibrations.linearity_type)
1103def compare_detector_to_legacy(
1104 tc: unittest.TestCase,
1105 detector: Detector,
1106 legacy_detector: Any,
1107 *,
1108 is_raw_assembled: bool,
1109 expect_nominal_calibrations: bool = True,
1110) -> None:
1111 """Compare a `~.cameras.Detector` to a `lsst.afw.cameraGeom.Detector`."""
1112 from lsst.afw.cameraGeom import FIELD_ANGLE, FOCAL_PLANE, PIXELS
1114 tc.assertEqual(legacy_detector.getName(), detector.name)
1115 tc.assertEqual(legacy_detector.getId(), detector.id)
1116 tc.assertEqual(DetectorType.from_legacy(legacy_detector.getType()), detector.type)
1117 tc.assertEqual(Box.from_legacy(legacy_detector.getBBox()), detector.bbox)
1118 tc.assertEqual(legacy_detector.getSerial(), detector.serial)
1119 legacy_orientation = legacy_detector.getOrientation()
1120 tc.assertEqual(legacy_orientation.getFpPosition3().getX(), detector.orientation.focal_plane_x)
1121 tc.assertEqual(legacy_orientation.getFpPosition3().getY(), detector.orientation.focal_plane_y)
1122 tc.assertEqual(legacy_orientation.getFpPosition3().getZ(), detector.orientation.focal_plane_z)
1123 tc.assertEqual(legacy_orientation.getReferencePoint().getX(), detector.orientation.pixel_reference_x)
1124 tc.assertEqual(legacy_orientation.getReferencePoint().getY(), detector.orientation.pixel_reference_y)
1125 tc.assertEqual(legacy_orientation.getYaw().asRadians(), detector.orientation.yaw.to_value(u.rad))
1126 tc.assertEqual(legacy_orientation.getPitch().asRadians(), detector.orientation.pitch.to_value(u.rad))
1127 tc.assertEqual(legacy_orientation.getRoll().asRadians(), detector.orientation.roll.to_value(u.rad))
1128 tc.assertEqual(legacy_detector.getPixelSize().getX(), detector.pixel_size)
1129 tc.assertEqual(legacy_detector.getPhysicalType(), detector.physical_type)
1130 for amplifier, legacy_amplifier in zip(detector.amplifiers, legacy_detector.getAmplifiers(), strict=True):
1131 compare_amplifier_to_legacy(
1132 tc,
1133 amplifier,
1134 legacy_amplifier,
1135 is_raw_assembled=is_raw_assembled,
1136 expect_nominal_calibrations=expect_nominal_calibrations,
1137 )
1138 pixel_xy = detector.bbox.meshgrid(n=3).map(lambda z: z.ravel().astype(np.float64))
1139 pixel_legacy_points = arrays_to_legacy_points(y=pixel_xy.y, x=pixel_xy.x)
1140 fp_legacy_points = legacy_detector.transform(pixel_legacy_points, PIXELS, FOCAL_PLANE)
1141 check_transform(
1142 tc,
1143 detector.to_focal_plane,
1144 pixel_xy,
1145 legacy_points_to_xy_array(fp_legacy_points),
1146 detector.frame,
1147 detector.to_focal_plane.out_frame,
1148 in_atol=1e-9 * u.pix,
1149 out_atol=1e-7 * detector.to_focal_plane.out_frame.unit,
1150 )
1151 fa_legacy_points = legacy_detector.transform(pixel_legacy_points, PIXELS, FIELD_ANGLE)
1152 check_transform(
1153 tc,
1154 detector.to_field_angle,
1155 pixel_xy,
1156 legacy_points_to_xy_array(fa_legacy_points),
1157 detector.frame,
1158 detector.to_field_angle.out_frame,
1159 in_atol=1e-9 * u.pix,
1160 out_atol=1e-7 * u.arcsec,
1161 )