LSST Applications g0f08755f38+82efc23009,g12f32b3c4e+e7bdf1200e,g1653933729+a8ce1bb630,g1a0ca8cf93+50eff2b06f,g28da252d5a+52db39f6a5,g2bbee38e9b+37c5a29d61,g2bc492864f+37c5a29d61,g2cdde0e794+c05ff076ad,g3156d2b45e+41e33cbcdc,g347aa1857d+37c5a29d61,g35bb328faa+a8ce1bb630,g3a166c0a6a+37c5a29d61,g3e281a1b8c+fb992f5633,g414038480c+7f03dfc1b0,g41af890bb2+11b950c980,g5fbc88fb19+17cd334064,g6b1c1869cb+12dd639c9a,g781aacb6e4+a8ce1bb630,g80478fca09+72e9651da0,g82479be7b0+04c31367b4,g858d7b2824+82efc23009,g9125e01d80+a8ce1bb630,g9726552aa6+8047e3811d,ga5288a1d22+e532dc0a0b,gae0086650b+a8ce1bb630,gb58c049af0+d64f4d3760,gc28159a63d+37c5a29d61,gcf0d15dbbd+2acd6d4d48,gd7358e8bfb+778a810b6e,gda3e153d99+82efc23009,gda6a2b7d83+2acd6d4d48,gdaeeff99f8+1711a396fd,ge2409df99d+6b12de1076,ge79ae78c31+37c5a29d61,gf0baf85859+d0a5978c5a,gf3967379c6+4954f8c433,gfb92a5be7c+82efc23009,gfec2e1e490+2aaed99252,w.2024.46
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, 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.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
85 is preferable.
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
100 ``fixed``.
101
102 Notes
103 -----
104 The standard pattern for constructing instances of this class is to use
105 `make_config_field`::
106
107 class SomeConfig(lsst.pex.config.Config):
108 packer = ObservationDimensionPacker.make_config_field()
109
110 class SomeTask(lsst.pipe.base.Task):
111 ConfigClass = SomeConfig
112
113 def run(self, ..., data_id: DataCoordinate):
114 packer = self.config.packer.apply(data_id)
115 packed_id = packer.pack(data_id)
116 ...
117
118 """
119
120 SUPPORTED_FILTERS = (
121 [None]
122 + list("ugrizyUBGVRIZYJHK") # split string into single chars
123 + [f"N{d}" for d in (387, 515, 656, 816, 921, 1010)] # HSC narrow-bands
124 + [f"N{d}" for d in (419, 540, 708, 964)] # DECam narrow-bands
125 )
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
128 provided.
129
130 This variable should no longer be modified to add new filters; pass
131 ``bands`` at construction or use `from_config` instead.
132 """
133
134 ConfigClass = SkyMapDimensionPackerConfig
135
137 self,
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,
144 ):
145 if bands is None:
146 bands = {b: i for i, b in enumerate(self.SUPPORTED_FILTERS)}
147 if dimensions is None:
148 if n_bands is None:
149 n_bands = len(bands)
150 dimension_names = ["tract", "patch"]
151 if n_bands != 0:
152 dimension_names.append("band")
153 dimensions = fixed.universe.conform(dimension_names)
154 else:
155 if "band" not in dimensions.names:
156 n_bands = 0
157 if dimensions.names != {"tract", "patch", "skymap"}:
158 raise ValueError(
159 f"Invalid dimensions for skymap dimension packer with n_bands=0: {dimensions}."
160 )
161 else:
162 if dimensions.names != {"tract", "patch", "skymap", "band"}:
163 raise ValueError(
164 f"Invalid dimensions for skymap dimension packer with n_bands>0: {dimensions}."
165 )
166 if n_bands is None:
167 n_bands = len(bands)
168 if n_tracts is None:
169 n_tracts = fixed.records["skymap"].tract_max
170 if n_patches is None:
171 n_patches = (
172 fixed.records["skymap"].patch_nx_max
173 * fixed.records["skymap"].patch_ny_max
174 )
175 super().__init__(fixed, dimensions)
176 self._bands = bands
177 self._n_bands = n_bands
178 self._n_tracts = n_tracts
179 self._n_patches = n_patches
180 self._bands_list = None
181
182 @classmethod
184 cls,
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.
188
189 Parameters
190 ----------
191 doc : `str`, optional
192 Documentation for the config field.
193
194 Returns
195 -------
196 field : `lsst.pex.config.ConfigurableField`
197 A config field whose instance values are [wrapper proxies to]
198 `SkyMapDimensionPackerConfig` instances.
199 """
200 return ConfigurableField(doc, target=cls.from_config, ConfigClass=cls.ConfigClass)
201
202 @classmethod
204 cls, data_id: DataCoordinate, config: SkyMapDimensionPackerConfig
205 ) -> SkyMapDimensionPacker:
206 """Construct a dimension packer from a config object and a data ID.
207
208 Parameters
209 ----------
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.
216
217 Returns
218 -------
219 packer : `SkyMapDimensionPackerConfig`
220 New dimension packer.
221
222 Notes
223 -----
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.
229 """
230 return cls(
231 data_id.subset(data_id.universe.conform(["skymap"])),
232 n_bands=config.n_bands,
233 bands=config.bands,
234 n_tracts=config.n_tracts,
235 n_patches=config.n_patches,
236 )
237
238 @property
239 def maxBits(self) -> int:
240 # Docstring inherited from DataIdPacker.maxBits
241 packedMax = self._n_tracts * self._n_patches
242 if self._n_bands:
243 packedMax *= self._n_bands
244 return (packedMax - 1).bit_length()
245
246 def _pack(self, dataId: DataCoordinate) -> int:
247 # Docstring inherited from DataIdPacker.pack
248 if dataId["patch"] >= self._n_patches:
249 raise ValueError(f"Patch ID {dataId['patch']} is out of bounds; expected <{self._n_patches}.")
250 if dataId["tract"] >= self._n_tracts:
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"]
253 if self._n_bands:
254 if (band_index := self._bands.get(dataId["band"])) is None:
255 raise ValueError(
256 f"Band {dataId['band']!r} is not supported by SkyMapDimensionPacker "
257 f"configuration; expected one of {list(self._bands)}."
258 )
259 if band_index >= self._n_bands:
260 raise ValueError(
261 f"Band index {band_index} for {dataId['band']!r} is out of bounds; "
262 f"expected <{self._n_bands}."
263 )
264 packed += self._bands[dataId["band"]] * self._n_patches * self._n_tracts
265 return packed
266
267 def unpack(self, packedId: int) -> DataCoordinate:
268 # Docstring inherited from DataIdPacker.unpack
269 d = {"skymap": self.fixed["skymap"]}
270 if self._n_bands:
271 index, packedId = divmod(packedId, (self._n_tracts * self._n_patches))
272 if self._bands_list is None:
273 self._bands_list = list(self._bands)
274 d["band"] = self._bands_list[index]
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)
Definition packers.py:144
ConfigurableField make_config_field(cls, str doc="How to pack tract, patch, and possibly band into an integer.")
Definition packers.py:186
SkyMapDimensionPacker from_config(cls, DataCoordinate data_id, SkyMapDimensionPackerConfig config)
Definition packers.py:205
int _pack(self, DataCoordinate dataId)
Definition packers.py:246
DataCoordinate unpack(self, int packedId)
Definition packers.py:267