Coverage for python / lsst / cell_coadds / _identifiers.py: 81%
53 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-24 08:40 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-24 08:40 +0000
1# This file is part of cell_coadds.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://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 <https://www.gnu.org/licenses/>.
22from __future__ import annotations
24__all__ = (
25 "PatchIdentifiers",
26 "CellIdentifiers",
27 "ObservationIdentifiers",
28)
31from dataclasses import dataclass
32from typing import Any, Self, cast
34from lsst.daf.butler import DataCoordinate, DimensionRecord
35from lsst.skymap import Index2D
38class BaseIdentifiers:
39 """Base class for identifiers. This acts like a mixin."""
41 def __getitem__(self, key: str) -> Any:
42 """Get an attribute by name.
44 Parameters
45 ----------
46 key : `str`
47 Name of the attribute to get.
49 Returns
50 -------
51 value : `int`, `float`, `str`, `~lsst.skymap.Index2D` or `None`
52 Value of the attribute.
53 """
54 return getattr(self, key)
57@dataclass(frozen=True)
58class PatchIdentifiers(BaseIdentifiers):
59 """Struct of identifiers for a coadd patch."""
61 skymap: str
62 """The name of the skymap this patch belongs to.
63 """
65 tract: int
66 """The name of the tract this patch belongs to.
67 """
69 patch: Index2D
70 """Identifiers for the patch itself.
71 """
73 band: str | None
74 """Name of the band, if any.
75 """
77 @classmethod
78 def from_data_id(cls, data_id: DataCoordinate) -> PatchIdentifiers:
79 """Construct from a data ID.
81 Parameters
82 ----------
83 data_id : `~lsst.daf.butler.DataCoordinate`
84 Fully-expanded data ID that includes the 'patch' dimension and
85 optionally the `band` dimension.
87 Returns
88 -------
89 identifiers : `PatchIdentifiers`
90 Struct of identifiers for this patch.
91 """
92 patch_record = cast(DimensionRecord, data_id.records["patch"])
93 return cls(
94 skymap=cast(str, data_id["skymap"]),
95 tract=cast(int, data_id["tract"]),
96 patch=Index2D(x=patch_record.cell_x, y=patch_record.cell_y),
97 band=cast(str, data_id.get("band")),
98 )
101@dataclass(frozen=True)
102class CellIdentifiers(PatchIdentifiers):
103 """Struct of identifiers for a coadd cell."""
105 cell: Index2D
106 """Identifiers for the cell itself."""
108 @classmethod
109 def from_data_id( # type: ignore [override]
110 cls, data_id: DataCoordinate, cell: Index2D
111 ) -> CellIdentifiers:
112 """Construct from a data ID and a cell index.
114 Parameters
115 ----------
116 data_id : `~lsst.daf.butler.DataCoordinate`
117 Fully-expanded data ID that includes the 'patch' dimension and
118 optionally the `band` dimension.
119 cell : `~lsst.skymap.Index2D`
120 Index of the cell within the patch.
122 Returns
123 -------
124 identifiers : `CellIdentifiers`
125 Struct of identifiers for this cell within a patch.
126 """
127 patch_record = cast(DimensionRecord, data_id.records["patch"])
128 return cls(
129 skymap=cast(str, data_id["skymap"]),
130 tract=cast(int, data_id["tract"]),
131 patch=Index2D(x=patch_record.cell_x, y=patch_record.cell_y),
132 band=cast(str, data_id.get("band")),
133 cell=cell,
134 )
137@dataclass(frozen=True)
138class ObservationIdentifiers(BaseIdentifiers):
139 """Struct of identifiers for an observation that contributed to a coadd
140 cell.
141 """
143 instrument: str
144 """Name of the instrument that this observation was taken with.
145 """
147 physical_filter: str
148 """Name of the physical filter that this observation was taken with.
149 """
151 visit: int
152 """Unique identifier for the visit.
154 A visit may be comprised of more than one exposure only if all were
155 observed back-to-back with no dithers, allowing them to be combined early
156 the processing with no resampling. All detector-level images in a visit
157 share the same visit ID.
158 """
160 day_obs: int
161 """A day and night of observations that rolls over during daylight hours.
162 The identifier is an decimal integer-concatenated date, i.e. YYYYMMDD,
163 with the exact rollover time observatory-dependent.
164 """
166 detector: int
167 """Unique identifier for the detector.
168 """
170 @property
171 def ccd(self) -> int:
172 """Alias for the detector.
174 This is provided for compatibility with the older API.
175 """
176 return self.detector
178 @classmethod
179 def from_data_id(cls, data_id: DataCoordinate, *, backup_detector: int = -1) -> ObservationIdentifiers:
180 """Construct from a data ID.
182 Parameters
183 ----------
184 data_id : `~lsst.daf.butler.DataCoordinate`
185 Fully-expanded data ID that includes the 'visit', 'detector' and
186 'day_obs' dimensions.
187 backup_detector : `int`, optional
188 Detector ID to use as a backup if not present in ``data_id``.
189 This is not used if detector information is available in
190 ``data_id`` and does not override it.
192 Returns
193 -------
194 identifiers : `ObservationIdentifiers`
195 Struct of identifiers for this observation.
196 """
197 detector = data_id.get("detector", backup_detector)
198 day_obs = data_id.get("day_obs")
199 return cls(
200 instrument=cast(str, data_id["instrument"]),
201 physical_filter=cast(str, data_id["physical_filter"]),
202 visit=cast(int, data_id["visit"]),
203 day_obs=cast(int, day_obs),
204 detector=detector,
205 )
207 def __lt__(self, other: Self, /) -> bool:
208 return (self.visit, self.detector) < (other.visit, other.detector)