Coverage for python / lsst / images / tests / _checks.py: 13%
195 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-15 00:04 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-15 00:04 +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_images_equal",
18 "assert_masked_images_equal",
19 "assert_masks_equal",
20 "assert_projections_equal",
21 "check_astropy_wcs_interface",
22 "check_projection",
23 "check_transform",
24 "compare_image_to_legacy",
25 "compare_mask_to_legacy",
26 "compare_masked_image_to_legacy",
27 "compare_projection_to_legacy_wcs",
28 "compare_psf_to_legacy",
29 "compare_visit_image_to_legacy",
30 "legacy_coords_to_astropy",
31 "legacy_points_to_xy_array",
32)
34import unittest
35from collections.abc import Mapping
36from typing import Any, Literal, cast
38import astropy.units as u
39import astropy.wcs.wcsapi
40import numpy as np
41from astropy.coordinates import SkyCoord
43from .._geom import XY, Box
44from .._image import Image
45from .._mask import Mask, MaskPlane
46from .._masked_image import MaskedImage
47from .._transforms import DetectorFrame, Frame, Projection, SkyFrame, Transform
48from .._visit_image import VisitImage
49from ..psfs import PointSpreadFunction
52def assert_close(
53 tc: unittest.TestCase,
54 a: np.ndarray | u.Quantity | float,
55 b: np.ndarray | u.Quantity | float,
56 **kwargs: Any,
57) -> None:
58 """Test that two arrays, floats, or quantities are almost equal.
60 Parameters
61 ----------
62 tc
63 Test case object with assert methods to use.
64 a
65 Array, float, or quantity to compare.
66 b
67 Array, float, or quantity to compare.
68 **kwargs
69 Forwarded to `astropy.units.allclose`.
70 """
71 tc.assertTrue(u.allclose(a, b, **kwargs), msg=f"{a} != {b}")
74def assert_images_equal(
75 tc: unittest.TestCase,
76 a: Image,
77 b: Image,
78 *,
79 rtol: float = 0.0,
80 atol: float = 0.0,
81 expect_view: bool | Literal["array"] | None = None,
82) -> None:
83 """Assert that two images are equal or nearly equal."""
84 tc.assertEqual(a.bbox, b.bbox)
85 tc.assertEqual(a.unit, b.unit)
86 assert_projections_equal(tc, a.projection, b.projection)
87 if expect_view is not None:
88 tc.assertEqual(np.may_share_memory(a.array, b.array), bool(expect_view))
89 if expect_view == "array":
90 tc.assertEqual(a.metadata, b.metadata)
91 else:
92 tc.assertEqual(a.metadata is b.metadata, expect_view)
93 if not expect_view:
94 assert_close(tc, a.array, b.array, atol=atol, rtol=rtol)
95 tc.assertEqual(a.metadata, b.metadata)
98def assert_masks_equal(tc: unittest.TestCase, a: Mask, b: Mask) -> None:
99 """Assert that two masks are equal or nearly equal."""
100 tc.assertEqual(a.bbox, b.bbox)
101 tc.assertEqual(a.schema, b.schema)
102 tc.assertEqual(a.metadata, b.metadata)
103 assert_projections_equal(tc, a.projection, b.projection)
104 np.testing.assert_array_equal(a.array, b.array)
107def assert_masked_images_equal(
108 tc: unittest.TestCase,
109 a: MaskedImage,
110 b: MaskedImage,
111 *,
112 rtol: float = 0.0,
113 atol: float = 0.0,
114 expect_view: bool | None = None,
115) -> None:
116 """Assert that two masked images are equal or nearly equal."""
117 tc.assertEqual(a.metadata, b.metadata)
118 assert_projections_equal(tc, a.projection, b.projection)
119 assert_images_equal(tc, a.image, b.image, rtol=rtol, atol=atol, expect_view=expect_view)
120 assert_masks_equal(tc, a.mask, b.mask)
121 assert_images_equal(tc, a.variance, b.variance, rtol=rtol, atol=atol, expect_view=expect_view)
124def compare_image_to_legacy(
125 tc: unittest.TestCase, image: Image, legacy_image: Any, expect_view: bool | None = None
126) -> None:
127 """Compare an `.Image` object to a legacy `lsst.afw.image.Image` object."""
128 tc.assertEqual(image.bbox, Box.from_legacy(legacy_image.getBBox()))
129 if expect_view is not None:
130 tc.assertEqual(np.may_share_memory(image.array, legacy_image.array), expect_view)
131 if not expect_view:
132 np.testing.assert_array_equal(image.array, legacy_image.array)
135def compare_mask_to_legacy(
136 tc: unittest.TestCase, mask: Mask, legacy_mask: Any, plane_map: Mapping[str, MaskPlane] | None = None
137) -> None:
138 """Compare a `.Mask` object to a legacy `lsst.afw.image.Mask` object."""
139 tc.assertEqual(mask.bbox, Box.from_legacy(legacy_mask.getBBox()))
140 if plane_map is None:
141 plane_map = {plane.name: plane for plane in mask.schema if plane is not None}
142 for old_name, new_plane in plane_map.items():
143 np.testing.assert_array_equal(
144 (legacy_mask.array & legacy_mask.getPlaneBitMask(old_name)).astype(bool),
145 mask.get(new_plane.name),
146 )
149def compare_masked_image_to_legacy(
150 tc: unittest.TestCase,
151 masked_image: MaskedImage,
152 legacy_masked_image: Any,
153 *,
154 plane_map: Mapping[str, MaskPlane] | None = None,
155 expect_view: bool | None = None,
156 alternates: Mapping[str, Any] | None = None,
157) -> None:
158 """Compare a `.MaskedImage` object to a legacy `lsst.afw.image.MaskedImage`
159 object.
161 Parameters
162 ----------
163 tc
164 Test case to use for asserts.
165 masked_image
166 New image to test.
167 legacy_masked_image
168 Legacy image to test against.
169 plane_map
170 Mapping between new and legacy mask planes.
171 expect_view
172 Whether to test that the image and variance arrays do or do not share
173 memory.
174 alternates
175 A mapping of other versions of one or more (new) components to also
176 check against the legacy versions of those components.
177 """
178 compare_image_to_legacy(tc, masked_image.image, legacy_masked_image.getImage(), expect_view=expect_view)
179 compare_mask_to_legacy(tc, masked_image.mask, legacy_masked_image.getMask(), plane_map=plane_map)
180 compare_image_to_legacy(
181 tc, masked_image.variance, legacy_masked_image.getVariance(), expect_view=expect_view
182 )
183 if alternates:
184 if image := alternates.get("image"):
185 compare_image_to_legacy(tc, image, legacy_masked_image.getImage(), expect_view=expect_view)
186 if mask := alternates.get("mask"):
187 compare_mask_to_legacy(tc, mask, legacy_masked_image.getMask(), plane_map=plane_map)
188 if variance := alternates.get("variance"):
189 compare_image_to_legacy(tc, variance, legacy_masked_image.getVariance(), expect_view=expect_view)
192def compare_visit_image_to_legacy(
193 tc: unittest.TestCase,
194 visit_image: VisitImage,
195 legacy_exposure: Any,
196 *,
197 plane_map: Mapping[str, MaskPlane] | None = None,
198 expect_view: bool | None = None,
199 instrument: str,
200 visit: int,
201 detector: int,
202 alternates: Mapping[str, Any] | None = None,
203) -> None:
204 """Compare a `.VisitImage` object to a legacy `lsst.afw.image.Exposure`
205 object.
207 Parameters
208 ----------
209 tc
210 Test case to use for asserts.
211 visit_image
212 New image to test.
213 legacy_exposure
214 Legacy image to test against.
215 plane_map
216 Mapping between new and legacy mask planes.
217 expect_view
218 Whether to test that the image and variance arrays do or do not share
219 memory.
220 instrument
221 Expected instrument name.
222 visit
223 Expected visit ID.
224 detetector
225 Expected detector ID.
226 alternates
227 A mapping of other versions of one or more (new) components to also
228 check against the legacy versions of those components.
229 """
230 compare_masked_image_to_legacy(
231 tc,
232 visit_image,
233 legacy_exposure.getMaskedImage(),
234 plane_map=plane_map,
235 expect_view=expect_view,
236 alternates=alternates,
237 )
238 detector_bbox = Box.from_legacy(legacy_exposure.getDetector().getBBox())
239 compare_projection_to_legacy_wcs(
240 tc,
241 visit_image.projection,
242 legacy_exposure.getWcs(),
243 DetectorFrame(instrument=instrument, visit=visit, detector=detector, bbox=detector_bbox),
244 visit_image.bbox,
245 )
246 tc.assertIs(visit_image.projection, visit_image.mask.projection)
247 tc.assertIs(visit_image.projection, visit_image.variance.projection)
248 compare_psf_to_legacy(tc, visit_image.psf, legacy_exposure.getPsf())
249 if alternates:
250 if projection := alternates.get("projection"):
251 compare_projection_to_legacy_wcs(
252 tc,
253 projection,
254 legacy_exposure.getWcs(),
255 DetectorFrame(instrument=instrument, visit=visit, detector=detector, bbox=detector_bbox),
256 visit_image.bbox,
257 )
258 if psf := alternates.get("psf"):
259 compare_psf_to_legacy(tc, psf, legacy_exposure.getPsf())
261 if obs_info := alternates.get("obs_info"):
262 visitInfo = legacy_exposure.visitInfo
263 tc.assertEqual(obs_info.instrument, visitInfo.getInstrumentLabel())
266def compare_psf_to_legacy(
267 tc: unittest.TestCase, psf: PointSpreadFunction, legacy_psf: Any, subimage_bbox: Box | None = None
268) -> int:
269 """Compare a PSF model object to its legacy interface.
271 Parameters
272 ----------
273 tc
274 Test case object with assert methods to use.
275 psf
276 Point-spread function to test.
277 legacy_psf
278 Legacy `lsst.afw.detection.Psf` instance to compare with.
279 subimage_bbox
280 Bounding box to draw test points from. Defaults to ``psf.bounds``.
282 Returns
283 -------
284 `int`
285 The number of points actually tested.
286 """
287 if subimage_bbox is None:
288 if isinstance(psf.bounds, Box):
289 subimage_bbox = psf.bounds
290 elif hasattr(psf.bounds, "bbox"):
291 subimage_bbox = cast(Box, psf.bounds.bbox)
292 else:
293 raise TypeError("No usable PSF bounds found; 'subimage_bbox' must be provided.")
295 # Pixel coordinates to test on over the subimage region of interest:
296 pixel_xy = subimage_bbox.meshgrid(step=50).map(np.ravel)
297 legacy_points = arrays_to_legacy_points(pixel_xy.x, pixel_xy.y)
299 n_points_tested: int = 0
300 for p in legacy_points:
301 if not psf.bounds.contains(x=p.x, y=p.y):
302 continue
303 tc.assertEqual(psf.kernel_bbox, Box.from_legacy(legacy_psf.computeKernelBBox(p)))
304 tc.assertEqual(
305 psf.compute_kernel_image(x=p.x, y=p.y), Image.from_legacy(legacy_psf.computeKernelImage(p))
306 )
307 tc.assertEqual(
308 psf.compute_stellar_bbox(x=p.x, y=p.y), Box.from_legacy(legacy_psf.computeImageBBox(p))
309 )
310 tc.assertEqual(psf.compute_stellar_image(x=p.x, y=p.y), Image.from_legacy(legacy_psf.computeImage(p)))
311 n_points_tested += 1
312 return n_points_tested
315def compare_projection_to_legacy_wcs[F: Frame](
316 tc: unittest.TestCase,
317 projection: Projection[F],
318 legacy_wcs: Any,
319 pixel_frame: F,
320 subimage_bbox: Box,
321 is_fits: bool = False,
322) -> None:
323 """Test a Projection object by comparing it to an equivalent
324 `lsst.afw.geom.SkyWcs`.
326 Parameters
327 ----------
328 tc
329 Test case object with assert methods to use.
330 projection
331 Projection to test.
332 legacy_wcs : ``lsst.afw.geom.SkyWcs``
333 Equivalent legacy WCS.
334 pixel_frame
335 Expected pixel frame for the projection.
336 subimage_bbox
337 Bounding box of points to generate for tests.
338 is_fits
339 Whether this projection is expected to be exactly representable as a
340 FIT WCS. If `False` it is assumed to have a FITS approximation
341 attached instead.
342 """
343 # Pixel coordinates to test on over the subimage region of interest:
344 pixel_xy = subimage_bbox.meshgrid(step=50).map(np.ravel)
345 # Array indices of those pixel values (subtract off bbox starts):
346 subimage_array_xy = XY(x=pixel_xy.x - subimage_bbox.x.start, y=pixel_xy.y - subimage_bbox.y.start)
347 sky_coords = legacy_coords_to_astropy(
348 legacy_wcs.pixelToSky(arrays_to_legacy_points(pixel_xy.x, pixel_xy.y))
349 )
350 # Test transforming with the Projection itself, which also tests its
351 # nested Transform and an Astropy High-Level WCS view with no origin
352 # change.
353 check_projection(tc, projection, pixel_xy, sky_coords, pixel_frame)
354 # Also test the Astropy High-Level WCS view with an origin change to
355 # array indices.
356 check_astropy_wcs_interface(
357 tc, projection.as_astropy(subimage_bbox), subimage_array_xy, sky_coords, pixel_atol=1e-5
358 )
359 if is_fits:
360 fits_wcs = projection.as_fits_wcs(subimage_bbox, allow_approximation=True)
361 assert fits_wcs is not None
362 check_astropy_wcs_interface(tc, fits_wcs, subimage_array_xy, sky_coords, pixel_atol=1e-5)
363 # Use that FITS approximation to check that we can make a
364 # Projection from a FITS WCS, too.
365 fits_projection = Projection.from_fits_wcs(fits_wcs, pixel_frame)
366 check_projection(
367 tc,
368 fits_projection,
369 subimage_array_xy,
370 sky_coords,
371 pixel_frame,
372 pixel_atol=1e-5,
373 )
374 # We want Projections we create from a FITS WCS to be backed by an
375 # AST FrameSet so we can convert them into legacy
376 # `lsst.afw.geom.SkyWcs` objects if desired.
377 tc.assertIn("Begin FrameSet", fits_projection.show())
378 else:
379 tc.assertIsNone(projection.as_fits_wcs(subimage_bbox, allow_approximation=False))
380 # The legacy SkyWcs should instead have a FITS approximation
381 # attached; run the same tests on that.
382 assert projection.fits_approximation is not None
383 compare_projection_to_legacy_wcs(
384 tc,
385 projection.fits_approximation,
386 legacy_wcs.getFitsApproximation(),
387 pixel_frame,
388 subimage_bbox,
389 is_fits=True,
390 )
393def check_transform[I: Frame, O: Frame](
394 tc: unittest.TestCase,
395 transform: Transform[I, O],
396 input_xy: XY[np.ndarray],
397 output_xy: XY[np.ndarray],
398 in_frame: Frame,
399 out_frame: Frame,
400 *,
401 check_inverted: bool = True,
402 in_atol: u.Quantity | None = None,
403 out_atol: u.Quantity | None = None,
404) -> None:
405 """Test Transform against known arrays of input and output points.
407 Parameters
408 ----------
409 tc
410 Test case object with assert methods to use.
411 transform
412 Transform to test.
413 input_xy
414 Arrays of input points.
415 outout_xy
416 Arrays of output points.
417 in_frame
418 Expected input frame.
419 out_frame
420 Expected output frame.
421 check_inverted
422 If `True`, recurse (once) to test the inverse transform.
423 in_atol
424 Expected absolute precision of input points.
425 out_atol
426 Expected absolute precision of output points.
427 """
428 tc.assertEqual(transform.in_frame, in_frame)
429 tc.assertEqual(transform.out_frame, out_frame)
430 in_atol_v = in_atol.to_value(in_frame.unit) if in_atol is not None else None
431 out_atol_v = out_atol.to_value(out_frame.unit) if out_atol is not None else None
432 # Test array interfaces.
433 test_output_xy = transform.apply_forward(x=input_xy.x, y=input_xy.y)
434 assert_close(tc, test_output_xy.x, output_xy.x, atol=out_atol_v)
435 assert_close(tc, test_output_xy.y, output_xy.y, atol=out_atol_v)
436 test_input_xy = transform.apply_inverse(x=output_xy.x, y=output_xy.y)
437 assert_close(tc, test_input_xy.x, input_xy.x, atol=in_atol_v)
438 assert_close(tc, test_input_xy.y, input_xy.y, atol=in_atol_v)
439 # Test scalar interfaces with numpy scalars.
440 for input_x, input_y, output_x, output_y in zip(input_xy.x, input_xy.y, output_xy.x, output_xy.y):
441 assert_close(tc, transform.apply_forward(x=input_x, y=input_y).x, output_x, atol=out_atol_v)
442 assert_close(tc, transform.apply_forward(x=input_x, y=input_y).y, output_y, atol=out_atol_v)
443 assert_close(tc, transform.apply_inverse(x=output_x, y=output_y).x, input_x, atol=in_atol_v)
444 assert_close(tc, transform.apply_inverse(x=output_x, y=output_y).y, input_y, atol=in_atol_v)
445 # Test quantity array interfaces.
446 input_q_xy = XY(x=input_xy.x * transform.in_frame.unit, y=input_xy.y * transform.in_frame.unit)
447 output_q_xy = XY(x=output_xy.x * transform.out_frame.unit, y=output_xy.y * transform.out_frame.unit)
448 test_output_q_xy = transform.apply_forward_q(x=input_q_xy.x, y=input_q_xy.y)
449 assert_close(tc, test_output_q_xy.x, output_q_xy.x, atol=out_atol)
450 assert_close(tc, test_output_q_xy.y, output_q_xy.y, atol=out_atol)
451 test_input_q_xy = transform.apply_inverse_q(x=output_q_xy.x, y=output_q_xy.y)
452 assert_close(tc, test_input_q_xy.x, input_q_xy.x, atol=in_atol)
453 assert_close(tc, test_input_q_xy.y, input_q_xy.y, atol=in_atol)
454 # Test quantity scalar interfaces.
455 for input_q_x, input_q_y, output_q_x, output_q_y in zip(
456 input_q_xy.x, input_q_xy.y, output_q_xy.x, output_q_xy.y
457 ):
458 assert_close(tc, transform.apply_forward_q(x=input_q_x, y=input_q_y).x, output_q_x, atol=out_atol)
459 assert_close(tc, transform.apply_forward_q(x=input_q_x, y=input_q_y).y, output_q_y, atol=out_atol)
460 assert_close(tc, transform.apply_inverse_q(x=output_q_x, y=output_q_y).x, input_q_x, atol=in_atol)
461 assert_close(tc, transform.apply_inverse_q(x=output_q_x, y=output_q_y).y, input_q_y, atol=in_atol)
462 if check_inverted:
463 # Test the inverse transform.
464 check_transform(
465 tc,
466 transform.inverted(),
467 output_xy,
468 input_xy,
469 out_frame,
470 in_frame,
471 check_inverted=False,
472 out_atol=in_atol,
473 in_atol=out_atol,
474 )
477def check_projection[P: Frame](
478 tc: unittest.TestCase,
479 projection: Projection[P],
480 pixel_xy: XY[np.ndarray],
481 sky_coords: SkyCoord,
482 pixel_frame: Frame,
483 *,
484 pixel_atol: float | None = None,
485 sky_atol: u.Quantity | None = None,
486) -> None:
487 """Test a `.Projection` instance against known arrays of pixel and sky
488 coordinates.
490 Parameters
491 ----------
492 tc
493 Test case object with assert methods to use.
494 projection
495 Projection to test.
496 pixel_xy
497 Arrays of pixel coordinates.
498 sky_coords
499 Corresponding sky coordinates.
500 pixel_frame
501 Expected pixel frame.
502 pixel_atol
503 Expected absolute precision of pixel points.
504 sky_atol
505 Expected absolute precision of sky coordinates.
506 """
507 tc.assertEqual(projection.pixel_frame, pixel_frame)
508 tc.assertEqual(projection.sky_frame, SkyFrame.ICRS)
509 sky_atol_v = sky_atol.to_value(SkyFrame.ICRS.unit) if sky_atol is not None else None
510 pixel_atol_q = pixel_atol * u.pix if pixel_atol is not None else None
511 # Test array interfaces.
512 test_pixel_xy = cast(XY[np.ndarray], projection.sky_to_pixel(sky_coords))
513 assert_close(tc, test_pixel_xy.x, pixel_xy.x, atol=pixel_atol)
514 assert_close(tc, test_pixel_xy.y, pixel_xy.y, atol=pixel_atol)
515 test_sky_astropy = projection.pixel_to_sky(x=pixel_xy.x, y=pixel_xy.y)
516 assert_close(tc, test_sky_astropy.ra, sky_coords.ra, atol=sky_atol_v)
517 assert_close(tc, test_sky_astropy.dec, sky_coords.dec, atol=sky_atol_v)
518 # Test scalar interfaces.
519 for pixel_x, pixel_y, sky_single in zip(pixel_xy.x, pixel_xy.y, sky_coords):
520 assert_close(tc, projection.sky_to_pixel(sky_single).x, pixel_x, atol=pixel_atol)
521 assert_close(tc, projection.sky_to_pixel(sky_single).y, pixel_y, atol=pixel_atol)
522 assert_close(tc, projection.pixel_to_sky(x=pixel_x, y=pixel_y).ra, sky_single.ra, atol=sky_atol_v)
523 assert_close(tc, projection.pixel_to_sky(x=pixel_x, y=pixel_y).dec, sky_single.dec, atol=sky_atol_v)
524 # Test the underlying Transform object.
525 sky_xy = XY(x=sky_coords.ra.to_value(u.rad), y=sky_coords.dec.to_value(u.rad))
526 check_transform(
527 tc,
528 projection.pixel_to_sky_transform,
529 pixel_xy,
530 sky_xy,
531 pixel_frame,
532 SkyFrame.ICRS,
533 check_inverted=False,
534 in_atol=pixel_atol_q,
535 out_atol=sky_atol,
536 )
537 check_transform(
538 tc,
539 projection.sky_to_pixel_transform,
540 sky_xy,
541 pixel_xy,
542 SkyFrame.ICRS,
543 pixel_frame,
544 check_inverted=False,
545 in_atol=sky_atol,
546 out_atol=pixel_atol_q,
547 )
548 # Test the Astropy interface adapter.
549 check_astropy_wcs_interface(
550 tc, projection.as_astropy(), pixel_xy, sky_coords, pixel_atol=pixel_atol, sky_atol=sky_atol
551 )
554def assert_projections_equal(
555 tc: unittest.TestCase,
556 a: Projection[Any] | None,
557 b: Projection[Any] | None,
558 expect_identity: bool | None = None,
559) -> None:
560 """Test that two `.Projection` instances are equivalent."""
561 if a is None and b is None:
562 return
563 assert a is not None and b is not None
564 match expect_identity:
565 case True:
566 tc.assertIs(a, b)
567 return
568 case False:
569 tc.assertIsNot(a, b)
570 case None if a is b:
571 return
572 tc.assertEqual(a.pixel_frame, b.pixel_frame)
573 tc.assertEqual(a.show(simplified=True), b.show(simplified=True))
574 assert_projections_equal(
575 tc, a.fits_approximation, cast(Projection[Any], b.fits_approximation), expect_identity=False
576 )
579def check_astropy_wcs_interface(
580 tc: unittest.TestCase,
581 wcs: astropy.wcs.wcsapi.BaseHighLevelWCS,
582 pixel_xy: XY[np.ndarray],
583 sky_coords: SkyCoord,
584 *,
585 pixel_atol: float | None = None,
586 sky_atol: u.Quantity | None = None,
587) -> None:
588 """Test an Astropy WCS instance against known arrays of pixel and
589 sky coordinates.
591 Parameters
592 ----------
593 tc
594 Test case object with assert methods to use.
595 wcs
596 Astropy WCS object to test.
597 pixel_xy
598 Arrays of pixel coordinates.
599 sky_coords
600 Corresponding sky coordinates.
601 pixel_atol
602 Expected absolute precision of pixel points.
603 sky_atol
604 Expected absolute precision of sky coordinates.
605 """
606 test_x, test_y = wcs.world_to_pixel(sky_coords)
607 assert_close(tc, test_x, pixel_xy.x, atol=pixel_atol)
608 assert_close(tc, test_y, pixel_xy.y, atol=pixel_atol)
609 test_sky_coords = wcs.pixel_to_world(pixel_xy.x, pixel_xy.y)
610 assert_close(tc, test_sky_coords.ra, sky_coords.ra, atol=sky_atol)
611 assert_close(tc, test_sky_coords.dec, sky_coords.dec, atol=sky_atol)
614def legacy_points_to_xy_array(legacy_points: list[Any]) -> XY[np.ndarray]:
615 """Convert a list of ``lsst.geom.Point2D`` objects to an `.XY` array."""
616 return XY(x=np.array([p.x for p in legacy_points]), y=np.array([p.y for p in legacy_points]))
619def legacy_coords_to_astropy(legacy_coords: list[Any]) -> SkyCoord:
620 """Convert a list of ``lsst.geom.SpherePoint`` objects to an Astropy
621 coordinate object.
622 """
623 return SkyCoord(
624 ra=np.array([p.getRa().asRadians() for p in legacy_coords]) * u.rad,
625 dec=np.array([p.getDec().asRadians() for p in legacy_coords]) * u.rad,
626 )
629def arrays_to_legacy_points(x: np.ndarray, y: np.ndarray) -> list[Any]:
630 """Convert arrays of ``x`` and ``y`` to a list of ``lsst.geom.Point2D``."""
631 from lsst.geom import Point2D
633 return [Point2D(x=xv, y=yv) for xv, yv in zip(x, y)]