22from __future__
import annotations
24__all__ = (
"SkyMapDimensionPacker",)
26from collections.abc
import Mapping
29from lsst.daf.butler
import DimensionPacker, DimensionGraph, DimensionGroup, DataCoordinate
34 "Mapping from band name to integer to use in the packed ID. "
35 "The default (None) is to use a hard-coded list of common bands; "
36 "pipelines that can enumerate the set of bands they are likely to see "
37 "should override this.",
44 "Number of bands to reserve space for. "
45 "If zero, bands are not included in the packed integer at all. "
46 "If `None`, the size of 'bands' is used.",
52 "Number of tracts, or, more precisely, one greater than the maximum tract ID."
53 "Default (None) obtains this value from the skymap dimension record.",
59 "Number of patches per tract, or, more precisely, one greater than the maximum patch ID."
60 "Default (None) obtains this value from the skymap dimension record.",
68 """A `DimensionPacker` for tract, patch and optionally band,
73 fixed : `lsst.daf.butler.DataCoordinate`
74 Data ID that identifies just the ``skymap`` dimension. Must have
75 dimension records attached unless ``n_tracts`` and ``n_patches`` are
77 dimensions : `lsst.daf.butler.DimensionGraph`, or \
78 `lsst.daf.butler.DimensionGroup`, 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 fixed: DataCoordinate,
140 dimensions: DimensionGroup | DimensionGraph |
None =
None,
141 bands: Mapping[str, int] |
None =
None,
142 n_bands: int |
None =
None,
143 n_tracts: int |
None =
None,
144 n_patches: int |
None =
None,
148 if dimensions
is None:
151 dimension_names = [
"tract",
"patch"]
153 dimension_names.append(
"band")
154 dimensions = fixed.universe.conform(dimension_names)
156 if "band" not in dimensions.names:
158 if dimensions.names != {
"tract",
"patch",
"skymap"}:
160 f
"Invalid dimensions for skymap dimension packer with n_bands=0: {dimensions}."
163 if dimensions.names != {
"tract",
"patch",
"skymap",
"band"}:
165 f
"Invalid dimensions for skymap dimension packer with n_bands>0: {dimensions}."
170 n_tracts = fixed.records[
"skymap"].tract_max
171 if n_patches
is None:
173 fixed.records[
"skymap"].patch_nx_max
174 * fixed.records[
"skymap"].patch_ny_max
186 doc: str =
"How to pack tract, patch, and possibly band into an integer."
187 ) -> ConfigurableField:
188 """Make a config field to control how skymap data IDs are packed.
192 doc : `str`, optional
193 Documentation for the config field.
197 field : `lsst.pex.config.ConfigurableField`
198 A config field whose instance values are [wrapper proxies to]
199 `SkyMapDimensionPackerConfig` instances.
205 cls, data_id: DataCoordinate, config: SkyMapDimensionPackerConfig
206 ) -> SkyMapDimensionPacker:
207 """Construct a dimension packer from a config object and a data ID.
211 data_id : `lsst.daf.butler.DataCoordinate`
212 Data ID that identifies at least the ``skymap`` dimension. Must
213 have dimension records attached unless ``config.n_tracts`` and
214 ``config.n_patches`` are both not `None`.
215 config : `SkyMapDimensionPackerConfig`
216 Configuration object.
220 packer : `SkyMapDimensionPackerConfig`
221 New dimension packer.
225 This interface is provided for consistency with the `lsst.pex.config`
226 "Configurable" concept, and is invoked when ``apply(data_id)`` is
227 called on a config instance attribute that corresponds to a field
228 created by `make_config_field`. The constructor signature cannot play
229 this role easily for backwards compatibility reasons.
232 data_id.subset(data_id.universe.conform([
"skymap"])),
233 n_bands=config.n_bands,
235 n_tracts=config.n_tracts,
236 n_patches=config.n_patches,
245 return (packedMax - 1).bit_length()
247 def _pack(self, dataId: DataCoordinate) -> int:
250 raise ValueError(f
"Patch ID {dataId['patch']} is out of bounds; expected <{self._n_patches}.")
252 raise ValueError(f
"Tract ID {dataId['tract']} is out of bounds; expected <{self._n_tracts}.")
253 packed = dataId[
"patch"] + self.
_n_patches * dataId[
"tract"]
255 if (band_index := self.
_bands.get(dataId[
"band"]))
is None:
257 f
"Band {dataId['band']!r} is not supported by SkyMapDimensionPacker "
258 f
"configuration; expected one of {list(self._bands)}."
262 f
"Band index {band_index} for {dataId['band']!r} is out of bounds; "
263 f
"expected <{self._n_bands}."
268 def unpack(self, packedId: int) -> DataCoordinate:
270 d = {
"skymap": self.fixed[
"skymap"]}
276 d[
"tract"], d[
"patch"] = divmod(packedId, self.
_n_patches)
277 return DataCoordinate.standardize(d, dimensions=self.dimensions)
ConfigurableField make_config_field(cls, str doc="How to pack tract, patch, and possibly band into an integer.")
__init__(self, DataCoordinate fixed, DimensionGroup|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)
SkyMapDimensionPacker from_config(cls, DataCoordinate data_id, SkyMapDimensionPackerConfig config)
int _pack(self, DataCoordinate dataId)
DataCoordinate unpack(self, int packedId)