22from __future__
import annotations
24__all__ = (
"SkyMapDimensionPacker",)
26from collections.abc
import Mapping
29from lsst.daf.butler
import DimensionPacker, DimensionGraph, DataCoordinate
30from deprecated.sphinx
import deprecated
35 "Mapping from band name to integer to use in the packed ID. "
36 "The default (None) is to use a hard-coded list of common bands; "
37 "pipelines that can enumerate the set of bands they are likely to see "
38 "should override this.",
45 "Number of bands to reserve space for. "
46 "If zero, bands are not included in the packed integer at all. "
47 "If `None`, the size of 'bands' is used.",
53 "Number of tracts, or, more precisely, one greater than the maximum tract ID."
54 "Default (None) obtains this value from the skymap dimension record.",
60 "Number of patches per tract, or, more precisely, one greater than the maximum patch ID."
61 "Default (None) obtains this value from the skymap dimension record.",
69 """A `DimensionPacker` for tract, patch and optionally band,
74 fixed : `lsst.daf.butler.DataCoordinate`
75 Data ID that identifies just the ``skymap`` dimension. Must have
76 dimension records attached unless ``n_tracts`` and ``n_patches`` are
78 dimensions : `lsst.daf.butler.DimensionGraph`, optional
79 The dimensions of data IDs packed by this instance. Must include
80 ``{skymap, tract, patch}``,
and may include ``band``. If
not provided,
81 this will be set to include ``band``
if ``n_bands != 0``.
82 bands : `~collections.abc.Mapping` [ `str`, `int` ]
or `
None`, optional
83 Mapping
from band name to integer to use
in the packed ID. `
None` uses
84 a fixed set of bands defined
in this
class. When calling code can
85 enumerate the bands it
is likely to see, providing an explicit mapping
87 n_bands : `int`
or `
None`, optional
88 The number of bands to leave room
for in the packed ID. If `
None`,
89 this will be set to ``len(bands)``. If ``0``, the band will
not be
90 included
in the dimensions at all. If ``1``, the band will be included
91 in the dimensions but will
not occupy any extra bits
in the packed ID.
92 This may be larger
or smaller than ``len(bands)``, to reserve extra
93 space
for the future
or align to byte boundaries,
or support a subset
94 of a larger mapping, respectively.
95 n_tracts : `int`
or `
None`, optional
96 The number of tracts to leave room
for in the packed ID. If `
None`,
97 this will be set via the ``skymap`` dimension record
in ``fixed``.
98 n_patches : `int`
or `
None`, optional
99 The number of patches (per tract) to leave room
for in the packed ID.
100 If `
None`, this will be set via the ``skymap`` dimension record
in
105 The standard pattern
for constructing instances of this
class is to use
106 `make_config_field`::
108 class SomeConfig(lsst.pex.config.Config):
109 packer = ObservationDimensionPacker.make_config_field()
111 class SomeTask(lsst.pipe.base.Task):
112 ConfigClass = SomeConfig
114 def run(self, ..., data_id: DataCoordinate):
115 packer = self.config.packer.apply(data_id)
116 packed_id = packer.pack(data_id)
121 SUPPORTED_FILTERS = (
123 + list(
"ugrizyUBGVRIZYJHK")
124 + [f
"N{d}" for d
in (387, 515, 656, 816, 921, 1010)]
125 + [f
"N{d}" for d
in (419, 540, 708, 964)]
127 """Sequence of supported bands used to construct a mapping from band name
128 to integer when the 'bands' config option
is `
None`
or no config
is
131 This variable should no longer be modified to add new filters;
pass
132 ``bands`` at construction
or use `from_config` instead.
135 ConfigClass = SkyMapDimensionPackerConfig
139 reason=
"This classmethod cannot reflect all __init__ args and will be removed after v27.",
141 category=FutureWarning,
144 """Return an integer that represents the band with the given
150 raise NotImplementedError(f
"band '{name}' not supported by this ID packer.")
154 reason=
"This classmethod cannot reflect all __init__ args and will be removed after v27.",
156 category=FutureWarning,
159 """Return an band name from its integer representation."""
164 reason=
"This classmethod cannot reflect all __init__ args and will be removed after v27.",
166 category=FutureWarning,
173 fixed: DataCoordinate,
174 dimensions: DimensionGraph |
None =
None,
175 bands: Mapping[str, int] |
None =
None,
176 n_bands: int |
None =
None,
177 n_tracts: int |
None =
None,
178 n_patches: int |
None =
None,
182 if dimensions
is None:
185 dimension_names = [
"tract",
"patch"]
187 dimension_names.append(
"band")
188 dimensions = fixed.universe.extract(dimension_names)
190 if "band" not in dimensions.names:
192 if dimensions.names != {
"tract",
"patch",
"skymap"}:
194 f
"Invalid dimensions for skymap dimension packer with n_bands=0: {dimensions}."
197 if dimensions.names != {
"tract",
"patch",
"skymap",
"band"}:
199 f
"Invalid dimensions for skymap dimension packer with n_bands>0: {dimensions}."
204 n_tracts = fixed.records[
"skymap"].tract_max
205 if n_patches
is None:
207 fixed.records[
"skymap"].patch_nx_max
208 * fixed.records[
"skymap"].patch_ny_max
220 doc: str =
"How to pack tract, patch, and possibly band into an integer."
221 ) -> ConfigurableField:
222 """Make a config field to control how skymap data IDs are packed.
226 doc : `str`, optional
227 Documentation for the config field.
231 field : `lsst.pex.config.ConfigurableField`
232 A config field whose instance values are [wrapper proxies to]
233 `SkyMapDimensionPackerConfig` instances.
239 cls, data_id: DataCoordinate, config: SkyMapDimensionPackerConfig
240 ) -> SkyMapDimensionPacker:
241 """Construct a dimension packer from a config object and a data ID.
245 data_id : `lsst.daf.butler.DataCoordinate`
246 Data ID that identifies at least the ``skymap`` dimension. Must
247 have dimension records attached unless ``config.n_tracts`` and
248 ``config.n_patches`` are both
not `
None`.
249 config : `SkyMapDimensionPackerConfig`
250 Configuration object.
254 packer : `SkyMapDimensionPackerConfig`
255 New dimension packer.
260 "Configurable" concept,
and is invoked when ``apply(data_id)``
is
261 called on a config instance attribute that corresponds to a field
262 created by `make_config_field`. The constructor signature cannot play
263 this role easily
for backwards compatibility reasons.
266 data_id.subset(data_id.universe.extract([
"skymap"])),
267 n_bands=config.n_bands,
269 n_tracts=config.n_tracts,
270 n_patches=config.n_patches,
279 return (packedMax - 1).bit_length()
281 def _pack(self, dataId: DataCoordinate) -> int:
284 raise ValueError(f
"Patch ID {dataId['patch']} is out of bounds; expected <{self._n_patches}.")
286 raise ValueError(f
"Tract ID {dataId['tract']} is out of bounds; expected <{self._n_tracts}.")
287 packed = dataId[
"patch"] + self.
_n_patches * dataId[
"tract"]
289 if (band_index := self.
_bands.get(dataId[
"band"]))
is None:
291 f
"Band {dataId['band']!r} is not supported by SkyMapDimensionPacker "
292 f
"configuration; expected one of {list(self._bands)}."
296 f
"Band index {band_index} for {dataId['band']!r} is out of bounds; "
297 f
"expected <{self._n_bands}."
302 def unpack(self, packedId: int) -> DataCoordinate:
304 d = {
"skymap": self.fixed[
"skymap"]}
310 d[
"tract"], d[
"patch"] = divmod(packedId, self.
_n_patches)
311 return DataCoordinate.standardize(d, graph=self.dimensions)
def getFilterNameFromInt(cls, num)
def __init__(self, DataCoordinate fixed, DimensionGraph|None dimensions=None, Mapping[str, int]|None bands=None, int|None n_bands=None, int|None n_tracts=None, int|None n_patches=None)
def getMaxIntForFilters(cls)
ConfigurableField make_config_field(cls, str doc="How to pack tract, patch, and possibly band into an integer.")
SkyMapDimensionPacker from_config(cls, DataCoordinate data_id, SkyMapDimensionPackerConfig config)
def getIntFromFilter(cls, name)
DataCoordinate unpack(self, int packedId)