22from __future__
import annotations
24__all__ = (
"SkyMapDimensionPacker",)
26from collections.abc
import Mapping
29from lsst.daf.butler
import DimensionPacker, 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.DimensionGroup`, optional
78 The dimensions of data IDs packed by this instance. Must include
79 ``{skymap, tract, patch}``, and may include ``band``. If not provided,
80 this will be set to include ``band`` if ``n_bands != 0``.
81 bands : `~collections.abc.Mapping` [ `str`, `int` ] or `None`, optional
82 Mapping from band name to integer to use in the packed ID. `None` uses
83 a fixed set of bands defined in this class. When calling code can
84 enumerate the bands it is likely to see, providing an explicit mapping
86 n_bands : `int` or `None`, optional
87 The number of bands to leave room for in the packed ID. If `None`,
88 this will be set to ``len(bands)``. If ``0``, the band will not be
89 included in the dimensions at all. If ``1``, the band will be included
90 in the dimensions but will not occupy any extra bits in the packed ID.
91 This may be larger or smaller than ``len(bands)``, to reserve extra
92 space for the future or align to byte boundaries, or support a subset
93 of a larger mapping, respectively.
94 n_tracts : `int` or `None`, optional
95 The number of tracts to leave room for in the packed ID. If `None`,
96 this will be set via the ``skymap`` dimension record in ``fixed``.
97 n_patches : `int` or `None`, optional
98 The number of patches (per tract) to leave room for in the packed ID.
99 If `None`, this will be set via the ``skymap`` dimension record in
104 The standard pattern for constructing instances of this class is to use
105 `make_config_field`::
107 class SomeConfig(lsst.pex.config.Config):
108 packer = ObservationDimensionPacker.make_config_field()
110 class SomeTask(lsst.pipe.base.Task):
111 ConfigClass = SomeConfig
113 def run(self, ..., data_id: DataCoordinate):
114 packer = self.config.packer.apply(data_id)
115 packed_id = packer.pack(data_id)
120 SUPPORTED_FILTERS = (
122 + list(
"ugrizyUBGVRIZYJHK")
123 + [f
"N{d}" for d
in (387, 515, 656, 816, 921, 1010)]
124 + [f
"N{d}" for d
in (419, 540, 708, 964)]
126 """Sequence of supported bands used to construct a mapping from band name
127 to integer when the 'bands' config option is `None` or no config is
130 This variable should no longer be modified to add new filters; pass
131 ``bands`` at construction or use `from_config` instead.
134 ConfigClass = SkyMapDimensionPackerConfig
138 fixed: DataCoordinate,
139 dimensions: DimensionGroup |
None =
None,
140 bands: Mapping[str, int] |
None =
None,
141 n_bands: int |
None =
None,
142 n_tracts: int |
None =
None,
143 n_patches: int |
None =
None,
147 if dimensions
is None:
150 dimension_names = [
"tract",
"patch"]
152 dimension_names.append(
"band")
153 dimensions = fixed.universe.conform(dimension_names)
155 if "band" not in dimensions.names:
157 if dimensions.names != {
"tract",
"patch",
"skymap"}:
159 f
"Invalid dimensions for skymap dimension packer with n_bands=0: {dimensions}."
162 if dimensions.names != {
"tract",
"patch",
"skymap",
"band"}:
164 f
"Invalid dimensions for skymap dimension packer with n_bands>0: {dimensions}."
169 n_tracts = fixed.records[
"skymap"].tract_max
170 if n_patches
is None:
172 fixed.records[
"skymap"].patch_nx_max
173 * fixed.records[
"skymap"].patch_ny_max
185 doc: str =
"How to pack tract, patch, and possibly band into an integer."
186 ) -> ConfigurableField:
187 """Make a config field to control how skymap data IDs are packed.
191 doc : `str`, optional
192 Documentation for the config field.
196 field : `lsst.pex.config.ConfigurableField`
197 A config field whose instance values are [wrapper proxies to]
198 `SkyMapDimensionPackerConfig` instances.
204 cls, data_id: DataCoordinate, config: SkyMapDimensionPackerConfig
205 ) -> SkyMapDimensionPacker:
206 """Construct a dimension packer from a config object and a data ID.
210 data_id : `lsst.daf.butler.DataCoordinate`
211 Data ID that identifies at least the ``skymap`` dimension. Must
212 have dimension records attached unless ``config.n_tracts`` and
213 ``config.n_patches`` are both not `None`.
214 config : `SkyMapDimensionPackerConfig`
215 Configuration object.
219 packer : `SkyMapDimensionPackerConfig`
220 New dimension packer.
224 This interface is provided for consistency with the `lsst.pex.config`
225 "Configurable" concept, and is invoked when ``apply(data_id)`` is
226 called on a config instance attribute that corresponds to a field
227 created by `make_config_field`. The constructor signature cannot play
228 this role easily for backwards compatibility reasons.
231 data_id.subset(data_id.universe.conform([
"skymap"])),
232 n_bands=config.n_bands,
234 n_tracts=config.n_tracts,
235 n_patches=config.n_patches,
244 return (packedMax - 1).bit_length()
246 def _pack(self, dataId: DataCoordinate) -> int:
249 raise ValueError(f
"Patch ID {dataId['patch']} is out of bounds; expected <{self._n_patches}.")
251 raise ValueError(f
"Tract ID {dataId['tract']} is out of bounds; expected <{self._n_tracts}.")
252 packed = dataId[
"patch"] + self.
_n_patches * dataId[
"tract"]
254 if (band_index := self.
_bands.get(dataId[
"band"]))
is None:
256 f
"Band {dataId['band']!r} is not supported by SkyMapDimensionPacker "
257 f
"configuration; expected one of {list(self._bands)}."
261 f
"Band index {band_index} for {dataId['band']!r} is out of bounds; "
262 f
"expected <{self._n_bands}."
267 def unpack(self, packedId: int) -> DataCoordinate:
269 d = {
"skymap": self.fixed[
"skymap"]}
275 d[
"tract"], d[
"patch"] = divmod(packedId, self.
_n_patches)
276 return DataCoordinate.standardize(d, dimensions=self.dimensions)
__init__(self, DataCoordinate fixed, DimensionGroup|None dimensions=None, Mapping[str, int]|None bands=None, int|None n_bands=None, int|None n_tracts=None, int|None n_patches=None)
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)
int _pack(self, DataCoordinate dataId)
DataCoordinate unpack(self, int packedId)