LSST Applications g180d380827+6621f76652,g2079a07aa2+86d27d4dc4,g2305ad1205+f5a9e323a1,g2bbee38e9b+c6a8a0fb72,g337abbeb29+c6a8a0fb72,g33d1c0ed96+c6a8a0fb72,g3a166c0a6a+c6a8a0fb72,g3ddfee87b4+9a10e1fe7b,g48712c4677+c9a099281a,g487adcacf7+f2e03ea30b,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+aead732c78,g64a986408d+eddffb812c,g858d7b2824+eddffb812c,g864b0138d7+aa38e45daa,g974c55ee3d+f37bf00e57,g99cad8db69+119519a52d,g9c22b2923f+e2510deafe,g9ddcbc5298+9a081db1e4,ga1e77700b3+03d07e1c1f,gb0e22166c9+60f28cb32d,gb23b769143+eddffb812c,gba4ed39666+c2a2e4ac27,gbb8dafda3b+27317ec8e9,gbd998247f1+585e252eca,gc120e1dc64+5817c176a8,gc28159a63d+c6a8a0fb72,gc3e9b769f7+6707aea8b4,gcf0d15dbbd+9a10e1fe7b,gdaeeff99f8+f9a426f77a,ge6526c86ff+6a2e01d432,ge79ae78c31+c6a8a0fb72,gee10cc3b42+585e252eca,gff1a9f87cc+eddffb812c,v27.0.0.rc1
LSST Data Management Base Package
Loading...
Searching...
No Matches
packers.py
Go to the documentation of this file.
1# This file is part of skymap.
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/>.
21
22from __future__ import annotations
23
24__all__ = ("SkyMapDimensionPacker",)
25
26from collections.abc import Mapping
27
28from lsst.pex.config import Config, Field, DictField, ConfigurableField
29from lsst.daf.butler import DimensionPacker, DimensionGraph, DimensionGroup, DataCoordinate
30
31
33 bands = DictField(
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.",
38 keytype=str,
39 itemtype=int,
40 default=None,
41 optional=True,
42 )
43 n_bands = Field(
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.",
47 dtype=int,
48 optional=True,
49 default=0,
50 )
51 n_tracts = Field(
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.",
54 dtype=int,
55 optional=True,
56 default=None,
57 )
58 n_patches = Field(
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.",
61 dtype=int,
62 optional=True,
63 default=None,
64 )
65
66
67class SkyMapDimensionPacker(DimensionPacker):
68 """A `DimensionPacker` for tract, patch and optionally band,
69 given a SkyMap.
70
71 Parameters
72 ----------
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
76 not `None`.
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
86 is preferable.
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
101 ``fixed``.
102
103 Notes
104 -----
105 The standard pattern for constructing instances of this class is to use
106 `make_config_field`::
107
108 class SomeConfig(lsst.pex.config.Config):
109 packer = ObservationDimensionPacker.make_config_field()
110
111 class SomeTask(lsst.pipe.base.Task):
112 ConfigClass = SomeConfig
113
114 def run(self, ..., data_id: DataCoordinate):
115 packer = self.config.packer.apply(data_id)
116 packed_id = packer.pack(data_id)
117 ...
118
119 """
120
121 SUPPORTED_FILTERS = (
122 [None]
123 + list("ugrizyUBGVRIZYJHK") # split string into single chars
124 + [f"N{d}" for d in (387, 515, 656, 816, 921, 1010)] # HSC narrow-bands
125 + [f"N{d}" for d in (419, 540, 708, 964)] # DECam narrow-bands
126 )
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
129 provided.
130
131 This variable should no longer be modified to add new filters; pass
132 ``bands`` at construction or use `from_config` instead.
133 """
134
135 ConfigClass = SkyMapDimensionPackerConfig
136
138 self,
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,
145 ):
146 if bands is None:
147 bands = {b: i for i, b in enumerate(self.SUPPORTED_FILTERS)}
148 if dimensions is None:
149 if n_bands is None:
150 n_bands = len(bands)
151 dimension_names = ["tract", "patch"]
152 if n_bands != 0:
153 dimension_names.append("band")
154 dimensions = fixed.universe.conform(dimension_names)
155 else:
156 if "band" not in dimensions.names:
157 n_bands = 0
158 if dimensions.names != {"tract", "patch", "skymap"}:
159 raise ValueError(
160 f"Invalid dimensions for skymap dimension packer with n_bands=0: {dimensions}."
161 )
162 else:
163 if dimensions.names != {"tract", "patch", "skymap", "band"}:
164 raise ValueError(
165 f"Invalid dimensions for skymap dimension packer with n_bands>0: {dimensions}."
166 )
167 if n_bands is None:
168 n_bands = len(bands)
169 if n_tracts is None:
170 n_tracts = fixed.records["skymap"].tract_max
171 if n_patches is None:
172 n_patches = (
173 fixed.records["skymap"].patch_nx_max
174 * fixed.records["skymap"].patch_ny_max
175 )
176 super().__init__(fixed, dimensions)
177 self._bands = bands
178 self._n_bands = n_bands
179 self._n_tracts = n_tracts
180 self._n_patches = n_patches
181 self._bands_list = None
182
183 @classmethod
185 cls,
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.
189
190 Parameters
191 ----------
192 doc : `str`, optional
193 Documentation for the config field.
194
195 Returns
196 -------
197 field : `lsst.pex.config.ConfigurableField`
198 A config field whose instance values are [wrapper proxies to]
199 `SkyMapDimensionPackerConfig` instances.
200 """
201 return ConfigurableField(doc, target=cls.from_config, ConfigClass=cls.ConfigClass)
202
203 @classmethod
205 cls, data_id: DataCoordinate, config: SkyMapDimensionPackerConfig
206 ) -> SkyMapDimensionPacker:
207 """Construct a dimension packer from a config object and a data ID.
208
209 Parameters
210 ----------
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.
217
218 Returns
219 -------
220 packer : `SkyMapDimensionPackerConfig`
221 New dimension packer.
222
223 Notes
224 -----
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.
230 """
231 return cls(
232 data_id.subset(data_id.universe.conform(["skymap"])),
233 n_bands=config.n_bands,
234 bands=config.bands,
235 n_tracts=config.n_tracts,
236 n_patches=config.n_patches,
237 )
238
239 @property
240 def maxBits(self) -> int:
241 # Docstring inherited from DataIdPacker.maxBits
242 packedMax = self._n_tracts * self._n_patches
243 if self._n_bands:
244 packedMax *= self._n_bands
245 return (packedMax - 1).bit_length()
246
247 def _pack(self, dataId: DataCoordinate) -> int:
248 # Docstring inherited from DataIdPacker.pack
249 if dataId["patch"] >= self._n_patches:
250 raise ValueError(f"Patch ID {dataId['patch']} is out of bounds; expected <{self._n_patches}.")
251 if dataId["tract"] >= self._n_tracts:
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"]
254 if self._n_bands:
255 if (band_index := self._bands.get(dataId["band"])) is None:
256 raise ValueError(
257 f"Band {dataId['band']!r} is not supported by SkyMapDimensionPacker "
258 f"configuration; expected one of {list(self._bands)}."
259 )
260 if band_index >= self._n_bands:
261 raise ValueError(
262 f"Band index {band_index} for {dataId['band']!r} is out of bounds; "
263 f"expected <{self._n_bands}."
264 )
265 packed += self._bands[dataId["band"]] * self._n_patches * self._n_tracts
266 return packed
267
268 def unpack(self, packedId: int) -> DataCoordinate:
269 # Docstring inherited from DataIdPacker.unpack
270 d = {"skymap": self.fixed["skymap"]}
271 if self._n_bands:
272 index, packedId = divmod(packedId, (self._n_tracts * self._n_patches))
273 if self._bands_list is None:
274 self._bands_list = list(self._bands)
275 d["band"] = self._bands_list[index]
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.")
Definition packers.py:187
__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)
Definition packers.py:145
SkyMapDimensionPacker from_config(cls, DataCoordinate data_id, SkyMapDimensionPackerConfig config)
Definition packers.py:206
int _pack(self, DataCoordinate dataId)
Definition packers.py:247
DataCoordinate unpack(self, int packedId)
Definition packers.py:268