Coverage for python/lsst/daf/butler/dimensions/_skypix.py: 46%
96 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-01 11:00 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-01 11:00 +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 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/>.
28from __future__ import annotations
30__all__ = (
31 "SkyPixDimension",
32 "SkyPixSystem",
33)
35from collections.abc import Iterator, Mapping, Set
36from types import MappingProxyType
37from typing import TYPE_CHECKING
39import sqlalchemy
40from lsst.sphgeom import PixelizationABC
41from lsst.utils import doImportType
43from .. import ddl
44from .._named import NamedValueAbstractSet, NamedValueSet
45from .._topology import TopologicalFamily, TopologicalSpace
46from ._elements import Dimension
47from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor
49if TYPE_CHECKING:
50 from ..registry.interfaces import SkyPixDimensionRecordStorage
51 from ._universe import DimensionUniverse
54class SkyPixSystem(TopologicalFamily):
55 """Class for hierarchical pixelization of the sky.
57 A `TopologicalFamily` that represents a hierarchical pixelization of the
58 sky.
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.PixelizationABC` subclass)
67 Class whose instances represent a particular level of this
68 pixelization.
69 """
71 def __init__(
72 self,
73 name: str,
74 *,
75 maxLevel: int,
76 PixelizationClass: type[PixelizationABC],
77 ):
78 super().__init__(name, TopologicalSpace.SPATIAL)
79 self.maxLevel = maxLevel
80 self.PixelizationClass = PixelizationClass
81 self._members: dict[int, SkyPixDimension] = {}
82 for level in range(maxLevel + 1):
83 self._members[level] = SkyPixDimension(self, level)
85 def choose(self, endpoints: Set[str], universe: DimensionUniverse) -> SkyPixDimension:
86 # Docstring inherited from TopologicalFamily.
87 best: SkyPixDimension | None = None
88 for endpoint_name in endpoints:
89 endpoint = universe[endpoint_name]
90 if endpoint not in self:
91 continue
92 assert isinstance(endpoint, SkyPixDimension)
93 if best is None or best.level < endpoint.level:
94 best = endpoint
95 if best is None:
96 raise RuntimeError(f"No recognized endpoints for {self.name} in {endpoints}.")
97 return best
99 def __getitem__(self, level: int) -> SkyPixDimension:
100 return self._members[level]
102 def __iter__(self) -> Iterator[SkyPixDimension]:
103 return iter(self._members.values())
105 def __len__(self) -> int:
106 return len(self._members)
109class SkyPixDimension(Dimension):
110 """Special dimension for sky pixelizations.
112 A special `Dimension` subclass for hierarchical pixelizations of the
113 sky at a particular level.
115 Unlike most other dimensions, skypix dimension records are not stored in
116 the database, as these records only contain an integer pixel ID and a
117 region on the sky, and each of these can be computed directly from the
118 other.
120 Parameters
121 ----------
122 system : `SkyPixSystem`
123 Pixelization system this dimension belongs to.
124 level : `int`
125 Integer level of this pixelization (smaller numbers are coarser grids).
126 """
128 def __init__(self, system: SkyPixSystem, level: int):
129 self.system = system
130 self.level = level
131 self.pixelization = system.PixelizationClass(level)
133 @property
134 def name(self) -> str:
135 return f"{self.system.name}{self.level}"
137 @property
138 def required(self) -> NamedValueAbstractSet[Dimension]:
139 # Docstring inherited from DimensionElement.
140 return NamedValueSet({self}).freeze()
142 @property
143 def implied(self) -> NamedValueAbstractSet[Dimension]:
144 # Docstring inherited from DimensionElement.
145 return NamedValueSet().freeze()
147 @property
148 def topology(self) -> Mapping[TopologicalSpace, TopologicalFamily]:
149 # Docstring inherited from TopologicalRelationshipEndpoint
150 return MappingProxyType({TopologicalSpace.SPATIAL: self.system})
152 @property
153 def metadata(self) -> NamedValueAbstractSet[ddl.FieldSpec]:
154 # Docstring inherited from DimensionElement.
155 return NamedValueSet().freeze()
157 def hasTable(self) -> bool:
158 # Docstring inherited from DimensionElement.hasTable.
159 return False
161 def makeStorage(self) -> SkyPixDimensionRecordStorage:
162 """Make the storage record.
164 Constructs the `DimensionRecordStorage` instance that should
165 be used to back this element in a registry.
167 Returns
168 -------
169 storage : `SkyPixDimensionRecordStorage`
170 Storage object that should back this element in a registry.
171 """
172 from ..registry.dimensions.skypix import BasicSkyPixDimensionRecordStorage
174 return BasicSkyPixDimensionRecordStorage(self)
176 @property
177 def uniqueKeys(self) -> NamedValueAbstractSet[ddl.FieldSpec]:
178 # Docstring inherited from DimensionElement.
179 return NamedValueSet(
180 {
181 ddl.FieldSpec(
182 name="id",
183 dtype=sqlalchemy.BigInteger,
184 primaryKey=True,
185 nullable=False,
186 )
187 }
188 ).freeze()
190 # Class attributes below are shadowed by instance attributes, and are
191 # present just to hold the docstrings for those instance attributes.
193 system: SkyPixSystem
194 """Pixelization system this dimension belongs to (`SkyPixSystem`).
195 """
197 level: int
198 """Integer level of this pixelization (smaller numbers are coarser grids).
199 """
201 pixelization: PixelizationABC
202 """Pixelization instance that can compute regions from IDs and IDs from
203 points (`sphgeom.PixelizationABC`).
204 """
207class SkyPixConstructionVisitor(DimensionConstructionVisitor):
208 """Builder visitor for a single `SkyPixSystem` and its dimensions.
210 Parameters
211 ----------
212 name : `str`
213 Name of the `SkyPixSystem` to be constructed.
214 pixelizationClassName : `str`
215 Fully-qualified name of the class whose instances represent a
216 particular level of this pixelization.
217 maxLevel : `int`, optional
218 Maximum level (inclusive) of the hierarchy. If not provided, an
219 attempt will be made to obtain it from a ``MAX_LEVEL`` attribute of the
220 pixelization class.
222 Notes
223 -----
224 At present, this class adds both a new `SkyPixSystem` instance all possible
225 `SkyPixDimension` to the builder that invokes it. In the future, it may
226 add only the `SkyPixSystem`, with dimension instances created on-the-fly by
227 the `DimensionUniverse`; this depends on eliminating assumptions about the
228 set of dimensions in a universe being static.
229 """
231 def __init__(self, name: str, pixelizationClassName: str, maxLevel: int | None = None):
232 super().__init__(name)
233 self._pixelizationClassName = pixelizationClassName
234 self._maxLevel = maxLevel
236 def hasDependenciesIn(self, others: Set[str]) -> bool:
237 # Docstring inherited from DimensionConstructionVisitor.
238 return False
240 def visit(self, builder: DimensionConstructionBuilder) -> None:
241 # Docstring inherited from DimensionConstructionVisitor.
242 PixelizationClass = doImportType(self._pixelizationClassName)
243 assert issubclass(PixelizationClass, PixelizationABC)
244 if self._maxLevel is not None:
245 maxLevel = self._maxLevel
246 else:
247 # MyPy does not know the return type of getattr.
248 max_level = getattr(PixelizationClass, "MAX_LEVEL", None)
249 if max_level is None:
250 raise TypeError(
251 f"Skypix pixelization class {self._pixelizationClassName} does"
252 " not have MAX_LEVEL but no max level has been set explicitly."
253 )
254 assert isinstance(max_level, int)
255 maxLevel = max_level
256 system = SkyPixSystem(
257 self.name,
258 maxLevel=maxLevel,
259 PixelizationClass=PixelizationClass,
260 )
261 builder.topology[TopologicalSpace.SPATIAL].add(system)
262 for level in range(maxLevel + 1):
263 dimension = system[level]
264 builder.dimensions.add(dimension)
265 builder.elements.add(dimension)