Coverage for python/lsst/daf/butler/dimensions/_skypix.py: 48%
98 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-16 10:44 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-16 10:44 +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
30from lsst.daf.butler.column_spec import IntColumnSpec
32__all__ = (
33 "SkyPixDimension",
34 "SkyPixSystem",
35)
37from collections.abc import Iterator, Mapping, Set
38from types import MappingProxyType
39from typing import TYPE_CHECKING
41from lsst.sphgeom import PixelizationABC
42from lsst.utils import doImportType
44from .._named import NamedValueAbstractSet, NamedValueSet
45from .._topology import TopologicalFamily, TopologicalSpace
46from ._elements import Dimension, KeyColumnSpec, MetadataColumnSpec
47from .construction import DimensionConstructionBuilder, DimensionConstructionVisitor
49if TYPE_CHECKING:
50 from ._universe import DimensionUniverse
53class SkyPixSystem(TopologicalFamily):
54 """Class for hierarchical pixelization of the sky.
56 A `TopologicalFamily` that represents a hierarchical pixelization of the
57 sky.
59 Parameters
60 ----------
61 name : `str`
62 Name of the system.
63 maxLevel : `int`
64 Maximum level (inclusive) of the hierarchy.
65 PixelizationClass : `type` (`lsst.sphgeom.PixelizationABC` subclass)
66 Class whose instances represent a particular level of this
67 pixelization.
68 """
70 def __init__(
71 self,
72 name: str,
73 *,
74 maxLevel: int,
75 PixelizationClass: type[PixelizationABC],
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)
84 def choose(self, endpoints: Set[str], universe: DimensionUniverse) -> SkyPixDimension:
85 # Docstring inherited from TopologicalFamily.
86 best: SkyPixDimension | None = None
87 for endpoint_name in endpoints:
88 endpoint = universe[endpoint_name]
89 if endpoint not in self:
90 continue
91 assert isinstance(endpoint, SkyPixDimension)
92 if best is None or best.level < endpoint.level:
93 best = endpoint
94 if best is None:
95 raise RuntimeError(f"No recognized endpoints for {self.name} in {endpoints}.")
96 return best
98 def __getitem__(self, level: int) -> SkyPixDimension:
99 return self._members[level]
101 def __iter__(self) -> Iterator[SkyPixDimension]:
102 return iter(self._members.values())
104 def __len__(self) -> int:
105 return len(self._members)
108class SkyPixDimension(Dimension):
109 """Special dimension for sky pixelizations.
111 A special `Dimension` subclass for hierarchical pixelizations of the
112 sky at a particular level.
114 Unlike most other dimensions, skypix dimension records are not stored in
115 the database, as these records only contain an integer pixel ID and a
116 region on the sky, and each of these can be computed directly from the
117 other.
119 Parameters
120 ----------
121 system : `SkyPixSystem`
122 Pixelization system this dimension belongs to.
123 level : `int`
124 Integer level of this pixelization (smaller numbers are coarser grids).
125 """
127 def __init__(self, system: SkyPixSystem, level: int):
128 self.system = system
129 self.level = level
130 self.pixelization = system.PixelizationClass(level)
132 @property
133 def name(self) -> str:
134 return f"{self.system.name}{self.level}"
136 @property
137 def required(self) -> NamedValueAbstractSet[Dimension]:
138 # Docstring inherited from DimensionElement.
139 return NamedValueSet({self}).freeze()
141 @property
142 def implied(self) -> NamedValueAbstractSet[Dimension]:
143 # Docstring inherited from DimensionElement.
144 return NamedValueSet().freeze()
146 @property
147 def topology(self) -> Mapping[TopologicalSpace, TopologicalFamily]:
148 # Docstring inherited from TopologicalRelationshipEndpoint
149 return MappingProxyType({TopologicalSpace.SPATIAL: self.system})
151 @property
152 def metadata_columns(self) -> NamedValueAbstractSet[MetadataColumnSpec]:
153 # Docstring inherited from DimensionElement.
154 return NamedValueSet().freeze()
156 @property
157 def documentation(self) -> str:
158 # Docstring inherited from DimensionElement.
159 return f"Level {self.level} of the {self.system.name!r} sky pixelization system."
161 def hasTable(self) -> bool:
162 # Docstring inherited from DimensionElement.hasTable.
163 return False
165 @property
166 def has_own_table(self) -> bool:
167 # Docstring inherited from DimensionElement.
168 return False
170 @property
171 def unique_keys(self) -> NamedValueAbstractSet[KeyColumnSpec]:
172 # Docstring inherited from DimensionElement.
173 return NamedValueSet([IntColumnSpec(name="id", nullable=False)]).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, an
204 attempt will be made to obtain it from a ``MAX_LEVEL`` attribute of the
205 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 by
212 the `DimensionUniverse`; this depends on eliminating assumptions about the
213 set of dimensions in a universe being static.
214 """
216 def __init__(self, name: str, pixelizationClassName: str, maxLevel: int | None = None):
217 super().__init__(name)
218 self._pixelizationClassName = pixelizationClassName
219 self._maxLevel = maxLevel
221 def hasDependenciesIn(self, others: Set[str]) -> bool:
222 # Docstring inherited from DimensionConstructionVisitor.
223 return False
225 def visit(self, builder: DimensionConstructionBuilder) -> None:
226 # Docstring inherited from DimensionConstructionVisitor.
227 PixelizationClass = doImportType(self._pixelizationClassName)
228 assert issubclass(PixelizationClass, PixelizationABC)
229 if self._maxLevel is not None:
230 maxLevel = self._maxLevel
231 else:
232 # MyPy does not know the return type of getattr.
233 max_level = getattr(PixelizationClass, "MAX_LEVEL", None)
234 if max_level is None:
235 raise TypeError(
236 f"Skypix pixelization class {self._pixelizationClassName} does"
237 " not have MAX_LEVEL but no max level has been set explicitly."
238 )
239 assert isinstance(max_level, int)
240 maxLevel = max_level
241 system = SkyPixSystem(
242 self.name,
243 maxLevel=maxLevel,
244 PixelizationClass=PixelizationClass,
245 )
246 builder.topology[TopologicalSpace.SPATIAL].add(system)
247 for level in range(maxLevel + 1):
248 dimension = system[level]
249 builder.dimensions.add(dimension)
250 builder.elements.add(dimension)