Coverage for tests/test_HealpixPixelization.py: 20%
101 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-07 01:41 -0800
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-07 01:41 -0800
1# This file is part of sphgeom.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
28import pickle
29import unittest
31import hpgeom as hpg
32import numpy as np
34try:
35 import yaml
36except ImportError:
37 yaml = None
39from lsst.sphgeom import Angle, Box, Circle, ConvexPolygon, Ellipse, HealpixPixelization, LonLat, UnitVector3d
42class HealpixPixelizationTestCase(unittest.TestCase):
43 """Test HEALPix pixelization."""
45 def test_construction(self):
46 """Test construction of a HealpixPixelization."""
47 with self.assertRaises(ValueError):
48 HealpixPixelization(-1)
49 h1 = HealpixPixelization(5)
50 self.assertEqual(h1.level, 5)
51 self.assertEqual(h1.getLevel(), 5)
52 self.assertEqual(h1.nside, 32)
53 h2 = HealpixPixelization(6)
54 h3 = HealpixPixelization(h2.level)
55 self.assertNotEqual(h1, h2)
56 self.assertEqual(h2, h3)
58 def test_indexing(self):
59 """Test indexing of HealpixPixelization."""
60 h = HealpixPixelization(5)
61 vec = UnitVector3d(1, 1, 1)
62 lonlat = LonLat(vec)
63 pix = hpg.angle_to_pixel(h.nside, lonlat.getLon().asDegrees(), lonlat.getLat().asDegrees())
64 self.assertEqual(h.index(UnitVector3d(1, 1, 1)), pix)
66 def test_pixel(self):
67 """Test pixel polygon of HealpixPixelization."""
68 h = HealpixPixelization(5)
69 pix_poly = h.pixel(10)
70 self.assertIsInstance(pix_poly, ConvexPolygon)
72 def test_envelope(self):
73 """Test envelope method of HealpixPixelization."""
74 # Make the hardest intersection: a region that _just_
75 # touches a healpix pixel.
76 h = HealpixPixelization(5)
77 pix = hpg.angle_to_pixel(h.nside, 50.0, 20.0)
79 corners_ra, corners_dec = hpg.boundaries(h.nside, pix, step=1)
81 # Take the southernmost corner...
82 smost = np.argmin(corners_dec)
84 # Choose challenging comparison box corners:
85 ra_range = np.array([corners_ra[smost] - 0.5, corners_ra[smost] + 0.5])
86 dec_range = np.array([corners_dec[smost] - 0.5, corners_dec[smost] + 1e-8])
88 # Test the box region
89 box = Box(
90 point1=LonLat.fromDegrees(ra_range[0], dec_range[0]),
91 point2=LonLat.fromDegrees(ra_range[1], dec_range[1]),
92 )
93 # These pixels have been checked to completely overlap the region
94 self._check_envelope(h, box, [98, 99, 104, 105])
96 # Try a polygon region:
97 poly = ConvexPolygon(
98 [
99 UnitVector3d(LonLat.fromDegrees(ra_range[0], dec_range[0])),
100 UnitVector3d(LonLat.fromDegrees(ra_range[1], dec_range[0])),
101 UnitVector3d(LonLat.fromDegrees(ra_range[1], dec_range[1])),
102 UnitVector3d(LonLat.fromDegrees(ra_range[0], dec_range[1])),
103 UnitVector3d(LonLat.fromDegrees(ra_range[0], dec_range[0])),
104 ]
105 )
106 self._check_envelope(h, poly, [98, 99, 104, 105])
108 # Try a circle region
109 circle = Circle(
110 center=UnitVector3d(
111 LonLat.fromDegrees((ra_range[0] + ra_range[1]) / 2.0, (dec_range[0] + dec_range[1]) / 2.0)
112 ),
113 angle=Angle.fromDegrees((dec_range[1] - dec_range[0]) / 2.0),
114 )
115 self._check_envelope(h, circle, [98, 99, 104, 105])
117 # Try a circle region with zero-radius (representing a point)
118 pt_circle = Circle(
119 center=UnitVector3d(
120 LonLat.fromDegrees((ra_range[0] + ra_range[1]) / 2.0, (dec_range[0] + dec_range[1]) / 2.0)
121 ),
122 )
123 self._check_envelope(h, pt_circle, [98])
125 def test_envelope_missing_neighbors(self):
126 """Test envelope, with a pixel with missing neighbors.
128 Testing DM-47043.
129 """
130 h = HealpixPixelization(0)
131 h_highres = HealpixPixelization(4)
133 self.assertEqual(h._nside_highres, h_highres._nside)
135 # Find a pixel with a missing neighbor at high res.
136 n = hpg.neighbors(h_highres._nside, np.arange(hpg.nside_to_npixel(h_highres._nside)))
137 ind = np.argmin(n)
138 self.assertEqual(n.ravel()[ind], -1)
140 # This is the pixel with a bad (-1) neighbor.
141 badpix = ind // 8
142 ra, dec = hpg.pixel_to_angle(h_highres._nside, badpix)
144 box = Box(
145 point1=LonLat.fromDegrees(ra - 0.1, dec - 0.1),
146 point2=LonLat.fromDegrees(ra + 0.1, dec + 0.1),
147 )
149 self._check_envelope(h, box, [0, 1, 5])
151 def _check_envelope(self, pixelization, region, check_pixels):
152 """Check the envelope from a region.
154 Parameters
155 ----------
156 pixelization : `lsst.sphgeom.HealpixPixelization`
157 region : `lsst.sphgeom.Region`
158 check_pixels : `list` [`int`]
159 """
160 pixel_range = pixelization.envelope(region)
162 pixels = []
163 for r in pixel_range.ranges():
164 pixels.extend(range(r[0], r[1]))
166 self.assertEqual(pixels, check_pixels)
168 def test_interior(self):
169 """Test interior method of HealpixPixelization."""
170 h = HealpixPixelization(5)
171 pix = hpg.angle_to_pixel(h.nside, 50.0, 20.0)
173 corners_ra, corners_dec = hpg.boundaries(h.nside, pix, step=1)
175 ra_range = np.array([corners_ra.min() - 1.0, corners_ra.max() + 1.0])
176 dec_range = np.array([corners_dec.min() - 1.0, corners_dec.max() + 1.0])
178 # Test the box region
179 box = Box(
180 point1=LonLat.fromDegrees(ra_range[0], dec_range[0]),
181 point2=LonLat.fromDegrees(ra_range[1], dec_range[1]),
182 )
183 # These pixels have been checked to completely overlap the region
184 self._check_interior(h, box, [pix])
186 # Try a polygon region:
187 poly = ConvexPolygon(
188 [
189 UnitVector3d(LonLat.fromDegrees(ra_range[0], dec_range[0])),
190 UnitVector3d(LonLat.fromDegrees(ra_range[1], dec_range[0])),
191 UnitVector3d(LonLat.fromDegrees(ra_range[1], dec_range[1])),
192 UnitVector3d(LonLat.fromDegrees(ra_range[0], dec_range[1])),
193 UnitVector3d(LonLat.fromDegrees(ra_range[0], dec_range[0])),
194 ]
195 )
196 self._check_interior(h, poly, [pix])
198 # Try a circle region
199 circle = Circle(
200 center=UnitVector3d(
201 LonLat.fromDegrees((ra_range[0] + ra_range[1]) / 2.0, (dec_range[0] + dec_range[1]) / 2.0)
202 ),
203 angle=Angle.fromDegrees(2.5),
204 )
205 self._check_interior(h, circle, [pix])
207 # Try an ellipse region
208 ellipse = Ellipse(
209 center=UnitVector3d(
210 LonLat.fromDegrees((ra_range[0] + ra_range[1]) / 2.0, (dec_range[0] + dec_range[1]) / 2.0)
211 ),
212 alpha=Angle.fromDegrees(1.5),
213 beta=Angle.fromDegrees(2.5),
214 orientation=Angle.fromDegrees(45.0),
215 )
216 self._check_interior(h, ellipse, [pix])
218 def _check_interior(self, pixelization, region, check_pixels):
219 """Check the interior from a region.
221 Parameters
222 ----------
223 pixelization : `lsst.sphgeom.HealpixPixelization`
224 region : `lsst.sphgeom.Region`
225 check_pixels : `list` [`int`]
226 """
227 pixel_range = pixelization.interior(region)
229 pixels = []
230 for r in pixel_range.ranges():
231 pixels.extend(range(r[0], r[1]))
233 self.assertEqual(pixels, check_pixels)
235 def test_index_to_string(self):
236 """Test converting index to string of HealpixPixelization."""
237 h = HealpixPixelization(5)
238 self.assertEqual(h.toString(0), str(0))
239 self.assertEqual(h.toString(100), str(100))
241 def test_string(self):
242 """Test string representation of HealpixPixelization."""
243 h = HealpixPixelization(5)
244 self.assertEqual(str(h), "HealpixPixelization(5)")
245 self.assertEqual(str(h), repr(h))
246 self.assertEqual(h, eval(repr(h), {"HealpixPixelization": HealpixPixelization}))
248 def test_pickle(self):
249 """Test pickling of HealpixPixelization."""
250 a = HealpixPixelization(5)
251 b = pickle.loads(pickle.dumps(a))
252 self.assertEqual(a, b)
254 @unittest.skipIf(not yaml, "YAML module can not be imported")
255 def test_yaml(self):
256 """Test yaml representation of HealpixPixelization."""
257 a = HealpixPixelization(5)
258 b = yaml.safe_load(yaml.dump(a))
259 self.assertEqual(a, b)
262if __name__ == "__main__":
263 unittest.main()