Coverage for python / lsst / cell_coadds / _identifiers.py: 81%

53 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-05 18:39 +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/>. 

21 

22from __future__ import annotations 

23 

24__all__ = ( 

25 "PatchIdentifiers", 

26 "CellIdentifiers", 

27 "ObservationIdentifiers", 

28) 

29 

30 

31from dataclasses import dataclass 

32from typing import Any, Self, cast 

33 

34from lsst.daf.butler import DataCoordinate, DimensionRecord 

35from lsst.skymap import Index2D 

36 

37 

38class BaseIdentifiers: 

39 """Base class for identifiers. This acts like a mixin.""" 

40 

41 def __getitem__(self, key: str) -> Any: 

42 """Get an attribute by name. 

43 

44 Parameters 

45 ---------- 

46 key : `str` 

47 Name of the attribute to get. 

48 

49 Returns 

50 ------- 

51 value : `int`, `float`, `str`, `~lsst.skymap.Index2D` or `None` 

52 Value of the attribute. 

53 """ 

54 return getattr(self, key) 

55 

56 

57@dataclass(frozen=True) 

58class PatchIdentifiers(BaseIdentifiers): 

59 """Struct of identifiers for a coadd patch.""" 

60 

61 skymap: str 

62 """The name of the skymap this patch belongs to. 

63 """ 

64 

65 tract: int 

66 """The name of the tract this patch belongs to. 

67 """ 

68 

69 patch: Index2D 

70 """Identifiers for the patch itself. 

71 """ 

72 

73 band: str | None 

74 """Name of the band, if any. 

75 """ 

76 

77 @classmethod 

78 def from_data_id(cls, data_id: DataCoordinate) -> PatchIdentifiers: 

79 """Construct from a data ID. 

80 

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. 

86 

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 ) 

99 

100 

101@dataclass(frozen=True) 

102class CellIdentifiers(PatchIdentifiers): 

103 """Struct of identifiers for a coadd cell.""" 

104 

105 cell: Index2D 

106 """Identifiers for the cell itself.""" 

107 

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. 

113 

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. 

121 

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 ) 

135 

136 

137@dataclass(frozen=True) 

138class ObservationIdentifiers(BaseIdentifiers): 

139 """Struct of identifiers for an observation that contributed to a coadd 

140 cell. 

141 """ 

142 

143 instrument: str 

144 """Name of the instrument that this observation was taken with. 

145 """ 

146 

147 physical_filter: str 

148 """Name of the physical filter that this observation was taken with. 

149 """ 

150 

151 visit: int 

152 """Unique identifier for the visit. 

153 

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 """ 

159 

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 """ 

165 

166 detector: int 

167 """Unique identifier for the detector. 

168 """ 

169 

170 @property 

171 def ccd(self) -> int: 

172 """Alias for the detector. 

173 

174 This is provided for compatibility with the older API. 

175 """ 

176 return self.detector 

177 

178 @classmethod 

179 def from_data_id(cls, data_id: DataCoordinate, *, backup_detector: int = -1) -> ObservationIdentifiers: 

180 """Construct from a data ID. 

181 

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. 

191 

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 ) 

206 

207 def __lt__(self, other: Self, /) -> bool: 

208 return (self.visit, self.detector) < (other.visit, other.detector)