Coverage for tests / test_image.py: 18%
92 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-23 08:41 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-23 08:41 +0000
1# This file is part of lsst-images.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12from __future__ import annotations
14import os
15import unittest
17import astropy.io.fits
18import astropy.units as u
19import numpy as np
20from astro_metadata_translator import ObservationInfo
22import lsst.utils.tests
23from lsst.images import Box, DetectorFrame, Image
24from lsst.images.tests import (
25 assert_close,
26 assert_images_equal,
27 assert_projections_equal,
28 compare_image_to_legacy,
29 make_random_projection,
30)
32DATA_DIR = os.environ.get("TESTDATA_IMAGES_DIR", None)
35class ImageTestCase(unittest.TestCase):
36 """Tests for the Image class."""
38 def test_basics(self):
39 """Test basic constructor patterns."""
40 image = Image(42, shape=(5, 5), metadata={"three": 3})
41 assert_close(self, image.array, np.zeros([5, 5], dtype=np.int64) + 42)
42 self.assertEqual(image.metadata["three"], 3)
44 data = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
45 image = Image(data)
46 subset = image[Box.factory[:3, 1:3]]
47 subset2 = image.absolute[:3, 1:3]
48 assert_images_equal(self, subset2, subset, expect_view=True)
50 assert_images_equal(self, image.copy(), image, expect_view=False)
52 # Add an explicit bounding box and then slice it.
53 image = Image(data, bbox=Box.factory[-2:1, 10:14])
54 with self.assertRaises(IndexError):
55 # Same slice no longer works in absolute slicing because we have
56 # moved origin.
57 image.absolute[:3, 1:3]
58 # That slice does still work in local coordinates.
59 assert_close(self, image.local[:3, 1:3].array, subset2.array)
60 # And we can write an equivalent slice in absolute coordinates.
61 assert_close(self, image.absolute[:0, 11:13].array, np.array([[2, 3], [6, 7]]))
63 # Test __eq__ behavior.
64 self.assertEqual(image[...], image)
65 self.assertEqual(image.__eq__(data), NotImplemented)
66 self.assertNotEqual(image, list(data))
68 with self.assertRaises(ValueError):
69 # bbox does not match array shape.
70 Image(np.array([[1, 2, 3], [4, 5, 6]]), bbox=Box.factory[0:2, 0:4])
72 with self.assertRaises(ValueError):
73 # shape does not match array shape.
74 Image(np.array([[2, 3, 4], [6, 7, 8]]), shape=[5, 2])
76 with self.assertRaises(TypeError):
77 # shape and bbox both None.
78 Image()
80 with self.assertRaises(ValueError):
81 # Shape mismatch.
82 Image(shape=[3, 6], bbox=Box.factory[-5:10, 0:10])
84 def test_quantity(self):
85 """Test quantities."""
86 data = np.array([[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0], [9.0, 10.0, 11.0, 12.0]])
87 data2 = data.copy() * 2.0
88 image = Image(data, unit=u.mJy, bbox=Box.factory[-2:1, 3:7])
90 q = image.quantity
91 self.assertEqual(q[1, 0], 5.0 * u.mJy)
92 image.quantity = image.array * 10.0 * u.uJy
93 q = image.quantity
94 self.assertEqual(q[1, 0], 0.05 * u.mJy)
96 image2 = Image(data2, unit=u.Jy)
97 image[Box.factory[-1:0, 5:7]] = image2.local[1:2, 2:4]
98 assert_close(
99 self,
100 image.array,
101 np.array([[0.01, 0.02, 0.03, 0.04], [0.05, 0.06, 14000.0, 16000.0], [0.09, 0.1, 0.11, 0.12]]),
102 )
104 def test_read_write(self):
105 """Round trip through file."""
106 data = np.array([[1.0, 2.0, np.nan, 4.0], [5.0, 6.0, 7.0, 8.0], [9.0, 10.0, 11.0, 12.0]])
107 md = {"int": 1, "float": 42.0, "bool": False, "long string header": "This is a string"}
108 obsinfo = ObservationInfo(telescope="Simonyi", instrument="LSSTCam", relative_humidity=23.5)
109 det_frame = DetectorFrame(instrument="Inst", visit=1234, detector=1, bbox=Box.factory[1:4096, 1:4096])
110 rng = np.random.default_rng(500)
111 projection = make_random_projection(rng, det_frame, Box.factory[1:4096, 1:4096])
113 image = Image(
114 data,
115 unit=u.nJy,
116 metadata=md,
117 obs_info=obsinfo,
118 bbox=Box.factory[-2:1, 3:7],
119 projection=projection,
120 )
122 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile:
123 image.write_fits(tmpFile)
125 new = Image.read_fits(tmpFile)
126 self.assertEqual(new, image)
128 # __eq__ does not test all components.
129 self.assertEqual(new.obs_info, image.obs_info)
130 self.assertEqual(new.metadata, image.metadata)
131 self.maxDiff = None
132 assert_projections_equal(self, new.projection, image.projection, expect_identity=False)
134 # Read subset.
135 subset = Image.read_fits(tmpFile, bbox=Box.factory[-2:0, 5:7])
136 self.assertEqual(subset, image.absolute[-2:0, 5:7])
137 self.assertEqual(subset, image.local[0:2, 2:4])
138 self.assertEqual(str(subset), "Image([y=-2:0, x=5:7], float64)")
139 self.assertEqual(
140 repr(subset),
141 "Image(..., bbox=Box(y=Interval(start=-2, stop=0), x=Interval(start=5, stop=7)), "
142 "dtype=dtype('float64'))",
143 )
145 # Check that WCS headers were written out.
146 with astropy.io.fits.open(tmpFile) as hdul:
147 hdu1 = hdul[1]
148 hdr1 = hdu1.header
149 self.assertEqual(hdr1["CTYPE1"], "RA---TAN")
151 @unittest.skipUnless(DATA_DIR is not None, "TESTDATA_IMAGES_DIR is not in the environment.")
152 def test_legacy(self) -> None:
153 """Test Image.read_legacy, Image.to_legacy, and Image.from_legacy."""
154 assert DATA_DIR is not None, "Guaranteed by decorator."
155 filename = os.path.join(DATA_DIR, "dp2", "legacy", "visit_image.fits")
156 det_frame = DetectorFrame(instrument="Inst", visit=1234, detector=1, bbox=Box.factory[1:4096, 1:4096])
157 image = Image.read_legacy(filename, preserve_quantization=True, fits_wcs_frame=det_frame)
158 try:
159 from lsst.afw.image import MaskedImageFitsReader
160 except ImportError:
161 raise unittest.SkipTest("'lsst.afw.image' could not be imported.") from None
162 reader = MaskedImageFitsReader(filename)
163 legacy_image = reader.readImage()
164 compare_image_to_legacy(self, image, legacy_image, expect_view=False)
165 # Converting back to afw will not share memory, because
166 # preserve_quantization=True makes the array read-only and to_legacy
167 # has to copy in that case.
168 compare_image_to_legacy(self, image, image.to_legacy(), expect_view=False)
169 # Converting from afw will always share memory.
170 image_view = Image.from_legacy(legacy_image)
171 compare_image_to_legacy(self, image_view, legacy_image, expect_view=True)
172 # Converting back to afw from the in-memory view will be another view.
173 compare_image_to_legacy(self, image_view, image_view.to_legacy(), expect_view=True)
176if __name__ == "__main__":
177 unittest.main()