Coverage for python/lsst/daf/butler/core/dimensions/_skypix.py: 46%
91 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-14 19:21 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-14 19:21 +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/>.
22from __future__ import annotations
24__all__ = (
25 "SkyPixDimension",
26 "SkyPixSystem",
27)
29from collections.abc import Mapping, Set
30from types import MappingProxyType
31from typing import TYPE_CHECKING
33import sqlalchemy
34from lsst.sphgeom import PixelizationABC
35from lsst.utils import doImportType
37from .. import ddl
38from .._topology import TopologicalFamily, TopologicalRelationshipEndpoint, TopologicalSpace
39from ..named import NamedValueAbstractSet, NamedValueSet
40from ._elements import Dimension
41from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor
43if TYPE_CHECKING:
44 from ...registry.interfaces import SkyPixDimensionRecordStorage
47class SkyPixSystem(TopologicalFamily):
48 """Class for hierarchical pixelization of the sky.
50 A `TopologicalFamily` that represents a hierarchical pixelization of the
51 sky.
53 Parameters
54 ----------
55 name : `str`
56 Name of the system.
57 maxLevel : `int`
58 Maximum level (inclusive) of the hierarchy.
59 PixelizationClass : `type` (`lsst.sphgeom.PixelizationABC` subclass)
60 Class whose instances represent a particular level of this
61 pixelization.
62 """
64 def __init__(
65 self,
66 name: str,
67 *,
68 maxLevel: int,
69 PixelizationClass: type[PixelizationABC],
70 ):
71 super().__init__(name, TopologicalSpace.SPATIAL)
72 self.maxLevel = maxLevel
73 self.PixelizationClass = PixelizationClass
74 self._members: dict[int, SkyPixDimension] = {}
75 for level in range(maxLevel + 1):
76 self._members[level] = SkyPixDimension(self, level)
78 def choose(self, endpoints: NamedValueAbstractSet[TopologicalRelationshipEndpoint]) -> SkyPixDimension:
79 # Docstring inherited from TopologicalFamily.
80 best: SkyPixDimension | None = None
81 for endpoint in endpoints:
82 if endpoint not in self:
83 continue
84 assert isinstance(endpoint, SkyPixDimension)
85 if best is None or best.level < endpoint.level:
86 best = endpoint
87 if best is None:
88 raise RuntimeError(f"No recognized endpoints for {self.name} in {endpoints}.")
89 return best
91 def __getitem__(self, level: int) -> SkyPixDimension:
92 return self._members[level]
95class SkyPixDimension(Dimension):
96 """Special dimension for sky pixelizations.
98 A special `Dimension` subclass for hierarchical pixelizations of the
99 sky at a particular level.
101 Unlike most other dimensions, skypix dimension records are not stored in
102 the database, as these records only contain an integer pixel ID and a
103 region on the sky, and each of these can be computed directly from the
104 other.
106 Parameters
107 ----------
108 system : `SkyPixSystem`
109 Pixelization system this dimension belongs to.
110 level : `int`
111 Integer level of this pixelization (smaller numbers are coarser grids).
112 """
114 def __init__(self, system: SkyPixSystem, level: int):
115 self.system = system
116 self.level = level
117 self.pixelization = system.PixelizationClass(level)
119 @property
120 def name(self) -> str:
121 return f"{self.system.name}{self.level}"
123 @property
124 def required(self) -> NamedValueAbstractSet[Dimension]:
125 # Docstring inherited from DimensionElement.
126 return NamedValueSet({self}).freeze()
128 @property
129 def implied(self) -> NamedValueAbstractSet[Dimension]:
130 # Docstring inherited from DimensionElement.
131 return NamedValueSet().freeze()
133 @property
134 def topology(self) -> Mapping[TopologicalSpace, TopologicalFamily]:
135 # Docstring inherited from TopologicalRelationshipEndpoint
136 return MappingProxyType({TopologicalSpace.SPATIAL: self.system})
138 @property
139 def metadata(self) -> NamedValueAbstractSet[ddl.FieldSpec]:
140 # Docstring inherited from DimensionElement.
141 return NamedValueSet().freeze()
143 def hasTable(self) -> bool:
144 # Docstring inherited from DimensionElement.hasTable.
145 return False
147 def makeStorage(self) -> SkyPixDimensionRecordStorage:
148 """Make the storage record.
150 Constructs the `DimensionRecordStorage` instance that should
151 be used to back this element in a registry.
153 Returns
154 -------
155 storage : `SkyPixDimensionRecordStorage`
156 Storage object that should back this element in a registry.
157 """
158 from ...registry.dimensions.skypix import BasicSkyPixDimensionRecordStorage
160 return BasicSkyPixDimensionRecordStorage(self)
162 @property
163 def uniqueKeys(self) -> NamedValueAbstractSet[ddl.FieldSpec]:
164 # Docstring inherited from DimensionElement.
165 return NamedValueSet(
166 {
167 ddl.FieldSpec(
168 name="id",
169 dtype=sqlalchemy.BigInteger,
170 primaryKey=True,
171 nullable=False,
172 )
173 }
174 ).freeze()
176 # Class attributes below are shadowed by instance attributes, and are
177 # present just to hold the docstrings for those instance attributes.
179 system: SkyPixSystem
180 """Pixelization system this dimension belongs to (`SkyPixSystem`).
181 """
183 level: int
184 """Integer level of this pixelization (smaller numbers are coarser grids).
185 """
187 pixelization: PixelizationABC
188 """Pixelization instance that can compute regions from IDs and IDs from
189 points (`sphgeom.PixelizationABC`).
190 """
193class SkyPixConstructionVisitor(DimensionConstructionVisitor):
194 """Builder visitor for a single `SkyPixSystem` and its dimensions.
196 Parameters
197 ----------
198 name : `str`
199 Name of the `SkyPixSystem` to be constructed.
200 pixelizationClassName : `str`
201 Fully-qualified name of the class whose instances represent a
202 particular level of this pixelization.
203 maxLevel : `int`, optional
204 Maximum level (inclusive) of the hierarchy. If not provided,
205 an attempt will be made to obtain it from a ``MAX_LEVEL`` attribute
206 of the pixelization class.
208 Notes
209 -----
210 At present, this class adds both a new `SkyPixSystem` instance all possible
211 `SkyPixDimension` to the builder that invokes it. In the future, it may
212 add only the `SkyPixSystem`, with dimension instances created on-the-fly
213 by the `DimensionUniverse`; this depends on `DimensionGraph.encode` going
214 away or otherwise eliminating assumptions about the set of dimensions in a
215 universe being static.
216 """
218 def __init__(self, name: str, pixelizationClassName: str, maxLevel: int | None = None):
219 super().__init__(name)
220 self._pixelizationClassName = pixelizationClassName
221 self._maxLevel = maxLevel
223 def hasDependenciesIn(self, others: Set[str]) -> bool:
224 # Docstring inherited from DimensionConstructionVisitor.
225 return False
227 def visit(self, builder: DimensionConstructionBuilder) -> None:
228 # Docstring inherited from DimensionConstructionVisitor.
229 PixelizationClass = doImportType(self._pixelizationClassName)
230 assert issubclass(PixelizationClass, PixelizationABC)
231 if self._maxLevel is not None:
232 maxLevel = self._maxLevel
233 else:
234 # MyPy does not know the return type of getattr.
235 max_level = getattr(PixelizationClass, "MAX_LEVEL", None)
236 if max_level is None:
237 raise TypeError(
238 f"Skypix pixelization class {self._pixelizationClassName} does"
239 " not have MAX_LEVEL but no max level has been set explicitly."
240 )
241 assert isinstance(max_level, int)
242 maxLevel = max_level
243 system = SkyPixSystem(
244 self.name,
245 maxLevel=maxLevel,
246 PixelizationClass=PixelizationClass,
247 )
248 builder.topology[TopologicalSpace.SPATIAL].add(system)
249 for level in range(maxLevel + 1):
250 dimension = system[level]
251 builder.dimensions.add(dimension)
252 builder.elements.add(dimension)