Coverage for python/lsst/daf/butler/core/dimensions/_skypix.py: 43%

83 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-12-01 19:55 +0000

1# This file is part of daf_butler. 

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 

22from __future__ import annotations 

23 

24__all__ = ( 

25 "SkyPixDimension", 

26 "SkyPixSystem", 

27) 

28 

29from types import MappingProxyType 

30from typing import ( 

31 AbstractSet, 

32 Dict, 

33 Mapping, 

34 Optional, 

35 Type, 

36 TYPE_CHECKING, 

37) 

38 

39import sqlalchemy 

40 

41from lsst.sphgeom import Pixelization 

42from lsst.utils import doImport 

43from .. import ddl 

44from .._topology import TopologicalFamily, TopologicalRelationshipEndpoint, TopologicalSpace 

45 

46from ..named import NamedValueAbstractSet, NamedValueSet 

47from ._elements import Dimension 

48from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor 

49 

50if TYPE_CHECKING: 50 ↛ 51line 50 didn't jump to line 51, because the condition on line 50 was never true

51 from ...registry.interfaces import SkyPixDimensionRecordStorage 

52 

53 

54class SkyPixSystem(TopologicalFamily): 

55 """Class for hierarchical pixelization of the sky. 

56 

57 A `TopologicalFamily` that represents a hierarchical pixelization of the 

58 sky. 

59 

60 Parameters 

61 ---------- 

62 name : `str` 

63 Name of the system. 

64 maxLevel : `int` 

65 Maximum level (inclusive) of the hierarchy. 

66 PixelizationClass : `type` (`lsst.sphgeom.Pixelization` subclass) 

67 Class whose instances represent a particular level of this 

68 pixelization. 

69 """ 

70 

71 def __init__( 

72 self, 

73 name: str, *, 

74 maxLevel: int, 

75 PixelizationClass: Type[Pixelization], 

76 ): 

77 super().__init__(name, TopologicalSpace.SPATIAL) 

78 self.maxLevel = maxLevel 

79 self.PixelizationClass = PixelizationClass 

80 self._members: Dict[int, SkyPixDimension] = {} 

81 for level in range(maxLevel + 1): 

82 self._members[level] = SkyPixDimension(self, level) 

83 

84 def choose(self, endpoints: NamedValueAbstractSet[TopologicalRelationshipEndpoint]) -> SkyPixDimension: 

85 # Docstring inherited from TopologicalFamily. 

86 best: Optional[SkyPixDimension] = None 

87 for endpoint in endpoints: 

88 if endpoint not in self: 

89 continue 

90 assert isinstance(endpoint, SkyPixDimension) 

91 if best is None or best.level < endpoint.level: 

92 best = endpoint 

93 if best is None: 

94 raise RuntimeError(f"No recognized endpoints for {self.name} in {endpoints}.") 

95 return best 

96 

97 def __getitem__(self, level: int) -> SkyPixDimension: 

98 return self._members[level] 

99 

100 

101class SkyPixDimension(Dimension): 

102 """Special dimension for sky pixelizations. 

103 

104 A special `Dimension` subclass for hierarchical pixelizations of the 

105 sky at a particular level. 

106 

107 Unlike most other dimensions, skypix dimension records are not stored in 

108 the database, as these records only contain an integer pixel ID and a 

109 region on the sky, and each of these can be computed directly from the 

110 other. 

111 

112 Parameters 

113 ---------- 

114 system : `SkyPixSystem` 

115 Pixelization system this dimension belongs to. 

116 level : `int` 

117 Integer level of this pixelization (smaller numbers are coarser grids). 

118 """ 

119 

120 def __init__(self, system: SkyPixSystem, level: int): 

121 self.system = system 

122 self.level = level 

123 self.pixelization = system.PixelizationClass(level) 

124 

125 @property 

126 def name(self) -> str: 

127 return f"{self.system.name}{self.level}" 

128 

129 @property 

130 def required(self) -> NamedValueAbstractSet[Dimension]: 

131 # Docstring inherited from DimensionElement. 

132 return NamedValueSet({self}).freeze() 

133 

134 @property 

135 def implied(self) -> NamedValueAbstractSet[Dimension]: 

136 # Docstring inherited from DimensionElement. 

137 return NamedValueSet().freeze() 

138 

139 @property 

140 def topology(self) -> Mapping[TopologicalSpace, TopologicalFamily]: 

141 # Docstring inherited from TopologicalRelationshipEndpoint 

142 return MappingProxyType({TopologicalSpace.SPATIAL: self.system}) 

143 

144 @property 

145 def metadata(self) -> NamedValueAbstractSet[ddl.FieldSpec]: 

146 # Docstring inherited from DimensionElement. 

147 return NamedValueSet().freeze() 

148 

149 def hasTable(self) -> bool: 

150 # Docstring inherited from DimensionElement.hasTable. 

151 return False 

152 

153 def makeStorage(self) -> SkyPixDimensionRecordStorage: 

154 """Make the storage record. 

155 

156 Constructs the `DimensionRecordStorage` instance that should 

157 be used to back this element in a registry. 

158 

159 Returns 

160 ------- 

161 storage : `SkyPixDimensionRecordStorage` 

162 Storage object that should back this element in a registry. 

163 """ 

164 from ...registry.dimensions.skypix import BasicSkyPixDimensionRecordStorage 

165 return BasicSkyPixDimensionRecordStorage(self) 

166 

167 @property 

168 def uniqueKeys(self) -> NamedValueAbstractSet[ddl.FieldSpec]: 

169 # Docstring inherited from DimensionElement. 

170 return NamedValueSet({ 

171 ddl.FieldSpec( 

172 name="id", 

173 dtype=sqlalchemy.BigInteger, 

174 primaryKey=True, 

175 nullable=False, 

176 ) 

177 }).freeze() 

178 

179 # Class attributes below are shadowed by instance attributes, and are 

180 # present just to hold the docstrings for those instance attributes. 

181 

182 system: SkyPixSystem 

183 """Pixelization system this dimension belongs to (`SkyPixSystem`). 

184 """ 

185 

186 level: int 

187 """Integer level of this pixelization (smaller numbers are coarser grids). 

188 """ 

189 

190 pixelization: Pixelization 

191 """Pixelization instance that can compute regions from IDs and IDs from 

192 points (`sphgeom.Pixelization`). 

193 """ 

194 

195 

196class SkyPixConstructionVisitor(DimensionConstructionVisitor): 

197 """Builder visitor for a single `SkyPixSystem` and its dimensions. 

198 

199 Parameters 

200 ---------- 

201 name : `str` 

202 Name of the `SkyPixSystem` to be constructed. 

203 pixelizationClassName : `str` 

204 Fully-qualified name of the class whose instances represent a 

205 particular level of this pixelization. 

206 maxLevel : `int`, optional 

207 Maximum level (inclusive) of the hierarchy. If not provided, 

208 an attempt will be made to obtain it from a ``MAX_LEVEL`` attribute 

209 of the pixelization class. 

210 

211 Notes 

212 ----- 

213 At present, this class adds both a new `SkyPixSystem` instance all possible 

214 `SkyPixDimension` to the builder that invokes it. In the future, it may 

215 add only the `SkyPixSystem`, with dimension instances created on-the-fly 

216 by the `DimensionUniverse`; this depends on `DimensionGraph.encode` going 

217 away or otherwise eliminating assumptions about the set of dimensions in a 

218 universe being static. 

219 """ 

220 

221 def __init__(self, name: str, pixelizationClassName: str, maxLevel: Optional[int] = None): 

222 super().__init__(name) 

223 self._pixelizationClassName = pixelizationClassName 

224 self._maxLevel = maxLevel 

225 

226 def hasDependenciesIn(self, others: AbstractSet[str]) -> bool: 

227 # Docstring inherited from DimensionConstructionVisitor. 

228 return False 

229 

230 def visit(self, builder: DimensionConstructionBuilder) -> None: 

231 # Docstring inherited from DimensionConstructionVisitor. 

232 PixelizationClass = doImport(self._pixelizationClassName) 

233 maxLevel = self._maxLevel if self._maxLevel is not None else PixelizationClass.MAX_LEVEL 

234 system = SkyPixSystem( 

235 self.name, 

236 maxLevel=maxLevel, 

237 PixelizationClass=PixelizationClass, 

238 ) 

239 builder.topology[TopologicalSpace.SPATIAL].add(system) 

240 for level in range(maxLevel + 1): 

241 dimension = system[level] 

242 builder.dimensions.add(dimension) 

243 builder.elements.add(dimension)