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