Coverage for python / lsst / images / tests / _checks.py: 13%
194 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-06 01:14 -0700
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-06 01:14 -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_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 else:
375 tc.assertIsNone(projection.as_fits_wcs(subimage_bbox, allow_approximation=False))
376 # The legacy SkyWcs should instead have a FITS approximation
377 # attached; run the same tests on that.
378 assert projection.fits_approximation is not None
379 compare_projection_to_legacy_wcs(
380 tc,
381 projection.fits_approximation,
382 legacy_wcs.getFitsApproximation(),
383 pixel_frame,
384 subimage_bbox,
385 is_fits=True,
386 )
389def check_transform[I: Frame, O: Frame](
390 tc: unittest.TestCase,
391 transform: Transform[I, O],
392 input_xy: XY[np.ndarray],
393 output_xy: XY[np.ndarray],
394 in_frame: Frame,
395 out_frame: Frame,
396 *,
397 check_inverted: bool = True,
398 in_atol: u.Quantity | None = None,
399 out_atol: u.Quantity | None = None,
400) -> None:
401 """Test Transform against known arrays of input and output points.
403 Parameters
404 ----------
405 tc
406 Test case object with assert methods to use.
407 transform
408 Transform to test.
409 input_xy
410 Arrays of input points.
411 outout_xy
412 Arrays of output points.
413 in_frame
414 Expected input frame.
415 out_frame
416 Expected output frame.
417 check_inverted
418 If `True`, recurse (once) to test the inverse transform.
419 in_atol
420 Expected absolute precision of input points.
421 out_atol
422 Expected absolute precision of output points.
423 """
424 tc.assertEqual(transform.in_frame, in_frame)
425 tc.assertEqual(transform.out_frame, out_frame)
426 in_atol_v = in_atol.to_value(in_frame.unit) if in_atol is not None else None
427 out_atol_v = out_atol.to_value(out_frame.unit) if out_atol is not None else None
428 # Test array interfaces.
429 test_output_xy = transform.apply_forward(x=input_xy.x, y=input_xy.y)
430 assert_close(tc, test_output_xy.x, output_xy.x, atol=out_atol_v)
431 assert_close(tc, test_output_xy.y, output_xy.y, atol=out_atol_v)
432 test_input_xy = transform.apply_inverse(x=output_xy.x, y=output_xy.y)
433 assert_close(tc, test_input_xy.x, input_xy.x, atol=in_atol_v)
434 assert_close(tc, test_input_xy.y, input_xy.y, atol=in_atol_v)
435 # Test scalar interfaces with numpy scalars.
436 for input_x, input_y, output_x, output_y in zip(input_xy.x, input_xy.y, output_xy.x, output_xy.y):
437 assert_close(tc, transform.apply_forward(x=input_x, y=input_y).x, output_x, atol=out_atol_v)
438 assert_close(tc, transform.apply_forward(x=input_x, y=input_y).y, output_y, atol=out_atol_v)
439 assert_close(tc, transform.apply_inverse(x=output_x, y=output_y).x, input_x, atol=in_atol_v)
440 assert_close(tc, transform.apply_inverse(x=output_x, y=output_y).y, input_y, atol=in_atol_v)
441 # Test quantity array interfaces.
442 input_q_xy = XY(x=input_xy.x * transform.in_frame.unit, y=input_xy.y * transform.in_frame.unit)
443 output_q_xy = XY(x=output_xy.x * transform.out_frame.unit, y=output_xy.y * transform.out_frame.unit)
444 test_output_q_xy = transform.apply_forward_q(x=input_q_xy.x, y=input_q_xy.y)
445 assert_close(tc, test_output_q_xy.x, output_q_xy.x, atol=out_atol)
446 assert_close(tc, test_output_q_xy.y, output_q_xy.y, atol=out_atol)
447 test_input_q_xy = transform.apply_inverse_q(x=output_q_xy.x, y=output_q_xy.y)
448 assert_close(tc, test_input_q_xy.x, input_q_xy.x, atol=in_atol)
449 assert_close(tc, test_input_q_xy.y, input_q_xy.y, atol=in_atol)
450 # Test quantity scalar interfaces.
451 for input_q_x, input_q_y, output_q_x, output_q_y in zip(
452 input_q_xy.x, input_q_xy.y, output_q_xy.x, output_q_xy.y
453 ):
454 assert_close(tc, transform.apply_forward_q(x=input_q_x, y=input_q_y).x, output_q_x, atol=out_atol)
455 assert_close(tc, transform.apply_forward_q(x=input_q_x, y=input_q_y).y, output_q_y, atol=out_atol)
456 assert_close(tc, transform.apply_inverse_q(x=output_q_x, y=output_q_y).x, input_q_x, atol=in_atol)
457 assert_close(tc, transform.apply_inverse_q(x=output_q_x, y=output_q_y).y, input_q_y, atol=in_atol)
458 if check_inverted:
459 # Test the inverse transform.
460 check_transform(
461 tc,
462 transform.inverted(),
463 output_xy,
464 input_xy,
465 out_frame,
466 in_frame,
467 check_inverted=False,
468 out_atol=in_atol,
469 in_atol=out_atol,
470 )
473def check_projection[P: Frame](
474 tc: unittest.TestCase,
475 projection: Projection[P],
476 pixel_xy: XY[np.ndarray],
477 sky_coords: SkyCoord,
478 pixel_frame: Frame,
479 *,
480 pixel_atol: float | None = None,
481 sky_atol: u.Quantity | None = None,
482) -> None:
483 """Test a `.Projection` instance against known arrays of pixel and sky
484 coordinates.
486 Parameters
487 ----------
488 tc
489 Test case object with assert methods to use.
490 projection
491 Projection to test.
492 pixel_xy
493 Arrays of pixel coordinates.
494 sky_coords
495 Corresponding sky coordinates.
496 pixel_frame
497 Expected pixel frame.
498 pixel_atol
499 Expected absolute precision of pixel points.
500 sky_atol
501 Expected absolute precision of sky coordinates.
502 """
503 tc.assertEqual(projection.pixel_frame, pixel_frame)
504 tc.assertEqual(projection.sky_frame, SkyFrame.ICRS)
505 sky_atol_v = sky_atol.to_value(SkyFrame.ICRS.unit) if sky_atol is not None else None
506 pixel_atol_q = pixel_atol * u.pix if pixel_atol is not None else None
507 # Test array interfaces.
508 test_pixel_xy = cast(XY[np.ndarray], projection.sky_to_pixel(sky_coords))
509 assert_close(tc, test_pixel_xy.x, pixel_xy.x, atol=pixel_atol)
510 assert_close(tc, test_pixel_xy.y, pixel_xy.y, atol=pixel_atol)
511 test_sky_astropy = projection.pixel_to_sky(x=pixel_xy.x, y=pixel_xy.y)
512 assert_close(tc, test_sky_astropy.ra, sky_coords.ra, atol=sky_atol_v)
513 assert_close(tc, test_sky_astropy.dec, sky_coords.dec, atol=sky_atol_v)
514 # Test scalar interfaces.
515 for pixel_x, pixel_y, sky_single in zip(pixel_xy.x, pixel_xy.y, sky_coords):
516 assert_close(tc, projection.sky_to_pixel(sky_single).x, pixel_x, atol=pixel_atol)
517 assert_close(tc, projection.sky_to_pixel(sky_single).y, pixel_y, atol=pixel_atol)
518 assert_close(tc, projection.pixel_to_sky(x=pixel_x, y=pixel_y).ra, sky_single.ra, atol=sky_atol_v)
519 assert_close(tc, projection.pixel_to_sky(x=pixel_x, y=pixel_y).dec, sky_single.dec, atol=sky_atol_v)
520 # Test the underlying Transform object.
521 sky_xy = XY(x=sky_coords.ra.to_value(u.rad), y=sky_coords.dec.to_value(u.rad))
522 check_transform(
523 tc,
524 projection.pixel_to_sky_transform,
525 pixel_xy,
526 sky_xy,
527 pixel_frame,
528 SkyFrame.ICRS,
529 check_inverted=False,
530 in_atol=pixel_atol_q,
531 out_atol=sky_atol,
532 )
533 check_transform(
534 tc,
535 projection.sky_to_pixel_transform,
536 sky_xy,
537 pixel_xy,
538 SkyFrame.ICRS,
539 pixel_frame,
540 check_inverted=False,
541 in_atol=sky_atol,
542 out_atol=pixel_atol_q,
543 )
544 # Test the Astropy interface adapter.
545 check_astropy_wcs_interface(
546 tc, projection.as_astropy(), pixel_xy, sky_coords, pixel_atol=pixel_atol, sky_atol=sky_atol
547 )
550def assert_projections_equal(
551 tc: unittest.TestCase,
552 a: Projection[Any] | None,
553 b: Projection[Any] | None,
554 expect_identity: bool | None = None,
555) -> None:
556 """Test that two `.Projection` instances are equivalent."""
557 if a is None and b is None:
558 return
559 assert a is not None and b is not None
560 match expect_identity:
561 case True:
562 tc.assertIs(a, b)
563 return
564 case False:
565 tc.assertIsNot(a, b)
566 case None if a is b:
567 return
568 tc.assertEqual(a.pixel_frame, b.pixel_frame)
569 tc.assertEqual(a.show(simplified=True), b.show(simplified=True))
570 assert_projections_equal(
571 tc, a.fits_approximation, cast(Projection[Any], b.fits_approximation), expect_identity=False
572 )
575def check_astropy_wcs_interface(
576 tc: unittest.TestCase,
577 wcs: astropy.wcs.wcsapi.BaseHighLevelWCS,
578 pixel_xy: XY[np.ndarray],
579 sky_coords: SkyCoord,
580 *,
581 pixel_atol: float | None = None,
582 sky_atol: u.Quantity | None = None,
583) -> None:
584 """Test an Astropy WCS instance against known arrays of pixel and
585 sky coordinates.
587 Parameters
588 ----------
589 tc
590 Test case object with assert methods to use.
591 wcs
592 Astropy WCS object to test.
593 pixel_xy
594 Arrays of pixel coordinates.
595 sky_coords
596 Corresponding sky coordinates.
597 pixel_atol
598 Expected absolute precision of pixel points.
599 sky_atol
600 Expected absolute precision of sky coordinates.
601 """
602 test_x, test_y = wcs.world_to_pixel(sky_coords)
603 assert_close(tc, test_x, pixel_xy.x, atol=pixel_atol)
604 assert_close(tc, test_y, pixel_xy.y, atol=pixel_atol)
605 test_sky_coords = wcs.pixel_to_world(pixel_xy.x, pixel_xy.y)
606 assert_close(tc, test_sky_coords.ra, sky_coords.ra, atol=sky_atol)
607 assert_close(tc, test_sky_coords.dec, sky_coords.dec, atol=sky_atol)
610def legacy_points_to_xy_array(legacy_points: list[Any]) -> XY[np.ndarray]:
611 """Convert a list of ``lsst.geom.Point2D`` objects to an `.XY` array."""
612 return XY(x=np.array([p.x for p in legacy_points]), y=np.array([p.y for p in legacy_points]))
615def legacy_coords_to_astropy(legacy_coords: list[Any]) -> SkyCoord:
616 """Convert a list of ``lsst.geom.SpherePoint`` objects to an Astropy
617 coordinate object.
618 """
619 return SkyCoord(
620 ra=np.array([p.getRa().asRadians() for p in legacy_coords]) * u.rad,
621 dec=np.array([p.getDec().asRadians() for p in legacy_coords]) * u.rad,
622 )
625def arrays_to_legacy_points(x: np.ndarray, y: np.ndarray) -> list[Any]:
626 """Convert arrays of ``x`` and ``y`` to a list of ``lsst.geom.Point2D``."""
627 from lsst.geom import Point2D
629 return [Point2D(x=xv, y=yv) for xv, yv in zip(x, y)]