Coverage for python/lsst/sphgeom/_healpixPixelization.py: 25%

68 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-12-14 03:21 -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 program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <http://www.gnu.org/licenses/>. 

21__all__ = ["HealpixPixelization"] 

22 

23import hpgeom as hpg 

24import numpy as np 

25 

26from ._sphgeom import Box, Circle, ConvexPolygon, Ellipse, LonLat, RangeSet, Region, UnitVector3d 

27from .pixelization_abc import PixelizationABC 

28 

29 

30class HealpixPixelization(PixelizationABC): 

31 """HEALPix pixelization class. 

32 

33 Parameters 

34 ---------- 

35 level : `int` 

36 Pixelization level. HEALPix nside = 2**level 

37 """ 

38 

39 MAX_LEVEL = 17 

40 

41 def __init__(self, level: int): 

42 if level < 0: 

43 raise ValueError("HealPix level must be >= 0.") 

44 

45 self._level = level 

46 self._nside = 2**level 

47 

48 self._npix = hpg.nside_to_npixel(self._nside) 

49 

50 # Values used to do pixel/region intersections 

51 self._bit_shift = 8 

52 self._nside_highres = self._nside * (2 ** (self._bit_shift // 2)) 

53 

54 @property 

55 def nside(self): 

56 return self._nside 

57 

58 def getLevel(self): 

59 return self._level 

60 

61 level = property(getLevel) 

62 

63 def universe(self) -> RangeSet: 

64 return RangeSet(0, self._npix) 

65 

66 def pixel(self, i) -> Region: 

67 # This is arbitrarily returning 4 points on a side 

68 # to approximate the pixel shape. 

69 varr = hpg.angle_to_vector(*hpg.boundaries(self._nside, i, step=4)) 

70 return ConvexPolygon([UnitVector3d(*c) for c in varr]) 

71 

72 def index(self, v: UnitVector3d) -> int: 

73 return hpg.vector_to_pixel(self._nside, v.x(), v.y(), v.z()) 

74 

75 def toString(self, i: int) -> str: 

76 return str(i) 

77 

78 def envelope(self, region: Region, maxRanges: int = 0): 

79 if maxRanges > 0: 

80 # If this is important, the rangeset can be consolidated. 

81 raise NotImplementedError("HealpixPixelization: maxRanges not implemented") 

82 pixels_highres = self._interior_pixels_from_region(self._nside_highres, region) 

83 

84 # Dilate the high resolution pixels by one to ensure that the full 

85 # region is completely covered at high resolution. 

86 neighbors = hpg.neighbors(self._nside_highres, pixels_highres) 

87 # Shift back to the original resolution and uniquify 

88 pixels = np.unique(np.right_shift(neighbors.ravel(), self._bit_shift)) 

89 

90 return RangeSet(pixels) 

91 

92 def interior(self, region: Region, maxRanges: int = 0): 

93 if maxRanges > 0: 

94 # If this is important, the rangeset can be consolidated. 

95 raise NotImplementedError("HealpixPixelization: maxRanges not implemented") 

96 pixels = self._interior_pixels_from_region(self._nside, region) 

97 

98 # Check that the corners of the pixels are entirely enclosed in 

99 # the region 

100 

101 # Returns arrays [npixels, ncorners], where ncorners is 4. 

102 corners_lon, corners_lat = hpg.boundaries(self._nside, pixels, step=1, degrees=False) 

103 

104 corners_int = region.contains(corners_lon.ravel(), corners_lat.ravel()).reshape((len(pixels), 4)) 

105 interior = np.sum(corners_int, axis=1) == 4 

106 pixels = pixels[interior] 

107 

108 return RangeSet(pixels) 

109 

110 def _interior_pixels_from_region(self, nside: int, region: Region): 

111 """Get interior pixels from a region. 

112 

113 Parameters 

114 ---------- 

115 nside : `int` 

116 Healpix nside to retrieve interior pixels. 

117 region : `lsst.sphgeom.Region` 

118 Sphgeom region to find interior pixels. 

119 

120 Returns 

121 ------- 

122 pixels : `np.ndarray` 

123 Array of pixels at resolution nside, nest ordering. 

124 """ 

125 if isinstance(region, Circle): 

126 center = LonLat(region.getCenter()) 

127 pixels = hpg.query_circle( 

128 nside, 

129 center.getLon().asRadians(), 

130 center.getLat().asRadians(), 

131 region.getOpeningAngle().asRadians(), 

132 degrees=False, 

133 ) 

134 elif isinstance(region, ConvexPolygon): 

135 vertices = np.array([[v.x(), v.y(), v.z()] for v in region.getVertices()]) 

136 pixels = hpg.query_polygon_vec(nside, vertices) 

137 elif isinstance(region, Box): 

138 pixels = hpg.query_box( 

139 nside, 

140 region.getLon().getA().asRadians(), 

141 region.getLon().getB().asRadians(), 

142 region.getLat().getA().asRadians(), 

143 region.getLat().getB().asRadians(), 

144 degrees=False, 

145 ) 

146 elif isinstance(region, Ellipse): 

147 # hpgeom supports query_ellipse given center, alpha, beta, 

148 # and orientation. However, until we figure out how to get 

149 # the orientation out of the Ellipse region, we will use the 

150 # bounding circle as was done with healpy. 

151 _circle = region.getBoundingCircle() 

152 center = LonLat(_circle.getCenter()) 

153 pixels = hpg.query_circle( 

154 nside, 

155 center.getLon().asRadians(), 

156 center.getLat().asRadians(), 

157 _circle.getOpeningAngle().asRadians(), 

158 degrees=False, 

159 ) 

160 else: 

161 raise ValueError("Invalid region.") 

162 

163 return pixels 

164 

165 def __eq__(self, other): 

166 if isinstance(other, HealpixPixelization): 

167 return self._level == other._level 

168 

169 def __repr__(self): 

170 return f"HealpixPixelization({self._level})" 

171 

172 def __reduce__(self): 

173 return (self.__class__, (self._level,))