Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of obs_base. 

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 <http://www.gnu.org/licenses/>. 

21from __future__ import annotations 

22 

23__all__ = ["CalibRepoConverter"] 

24 

25import os 

26import sqlite3 

27import astropy.time 

28from typing import TYPE_CHECKING, Dict, Iterator, Tuple, Optional 

29 

30from lsst.daf.butler import Butler as Butler3 

31 

32from .repoConverter import RepoConverter 

33from .repoWalker import RepoWalker 

34from .translators import makeCalibrationLabel 

35 

36if TYPE_CHECKING: 36 ↛ 37line 36 didn't jump to line 37, because the condition on line 36 was never true

37 from lsst.daf.butler import StorageClass, FormatterParameter 

38 from .repoWalker.scanner import PathElementHandler 

39 from ..cameraMapper import CameraMapper 

40 from ..mapping import Mapping as CameraMapperMapping # disambiguate from collections.abc.Mapping 

41 

42 

43class CalibRepoConverter(RepoConverter): 

44 """A specialization of `RepoConverter` for calibration repositories. 

45 

46 Parameters 

47 ---------- 

48 mapper : `CameraMapper` 

49 Gen2 mapper for the data repository. The root associated with the 

50 mapper is ignored and need not match the root of the repository. 

51 kwds 

52 Additional keyword arguments are forwarded to (and required by) 

53 `RepoConverter`. 

54 """ 

55 

56 def __init__(self, *, mapper: CameraMapper, **kwds): 

57 super().__init__(**kwds) 

58 self.mapper = mapper 

59 self._datasetTypes = set() 

60 

61 def isDatasetTypeSpecial(self, datasetTypeName: str) -> bool: 

62 # Docstring inherited from RepoConverter. 

63 return datasetTypeName in self.task.config.curatedCalibrations 

64 

65 def iterMappings(self) -> Iterator[Tuple[str, CameraMapperMapping]]: 

66 # Docstring inherited from RepoConverter. 

67 yield from self.mapper.calibrations.items() 

68 

69 def makeRepoWalkerTarget(self, datasetTypeName: str, template: str, keys: Dict[str, type], 

70 storageClass: StorageClass, formatter: FormatterParameter = None, 

71 targetHandler: Optional[PathElementHandler] = None, 

72 ) -> RepoWalker.Target: 

73 # Docstring inherited from RepoConverter. 

74 target = RepoWalker.Target( 

75 datasetTypeName=datasetTypeName, 

76 storageClass=storageClass, 

77 template=template, 

78 keys=keys, 

79 instrument=self.task.instrument.getName(), 

80 universe=self.task.registry.dimensions, 

81 formatter=formatter, 

82 targetHandler=targetHandler, 

83 ) 

84 self._datasetTypes.add(target.datasetType) 

85 return target 

86 

87 def insertDimensionData(self): 

88 # Docstring inherited from RepoConverter. 

89 # This has only been tested on HSC, and it's not clear how general it 

90 # is. The catch is that it needs to generate calibration_label strings 

91 # consistent with those produced by the Translator system. 

92 db = sqlite3.connect(os.path.join(self.root, "calibRegistry.sqlite3")) 

93 db.row_factory = sqlite3.Row 

94 records = [] 

95 for datasetType in self._datasetTypes: 

96 if "calibration_label" not in datasetType.dimensions: 

97 continue 

98 fields = ["validStart", "validEnd", "calibDate"] 

99 if "detector" in datasetType.dimensions.names: 

100 fields.append(self.task.config.ccdKey) 

101 else: 

102 fields.append(f"NULL AS {self.task.config.ccdKey}") 

103 if ("physical_filter" in datasetType.dimensions.names 

104 or "abstract_filter" in datasetType.dimensions.names): 

105 fields.append("filter") 

106 else: 

107 fields.append("NULL AS filter") 

108 query = f"SELECT DISTINCT {', '.join(fields)} FROM {datasetType.name};" 

109 try: 

110 results = db.execute(query) 

111 except sqlite3.OperationalError as e: 

112 if (self.mapper.mappings[datasetType.name].tables is None 

113 or len(self.mapper.mappings[datasetType.name].tables) == 0): 

114 self.task.log.warn("Could not extract calibration ranges for %s in %s: %r", 

115 datasetType.name, self.root, e) 

116 continue 

117 # Try using one of the alternate table names in the mapper (e.g. cpBias->bias for DECam). 

118 name = self.mapper.mappings[datasetType.name].tables[0] 

119 query = f"SELECT DISTINCT {', '.join(fields)} FROM {name};" 

120 try: 

121 results = db.execute(query) 

122 except sqlite3.OperationalError as e: 

123 self.task.log.warn("Could not extract calibration ranges for %s in %s: %r", 

124 datasetType.name, self.root, e) 

125 continue 

126 for row in results: 

127 label = makeCalibrationLabel(datasetType.name, row["calibDate"], 

128 ccd=row[self.task.config.ccdKey], filter=row["filter"]) 

129 # For validity times we use TAI as some gen2 repos have validity 

130 # dates very far in the past or future. 

131 day = astropy.time.TimeDelta(1, format="jd", scale="tai") 

132 records.append({ 

133 "instrument": self.task.instrument.getName(), 

134 "name": label, 

135 "datetime_begin": astropy.time.Time(row["validStart"], format="iso", scale="tai"), 

136 "datetime_end": astropy.time.Time(row["validEnd"], format="iso", scale="tai") + day 

137 }) 

138 if records: 

139 self.task.registry.insertDimensionData("calibration_label", *records) 

140 

141 def ingest(self): 

142 # Docstring inherited from RepoConverter. 

143 if self.task.config.doWriteCuratedCalibrations: 

144 butler3 = Butler3(butler=self.task.butler3, run=self._run) 

145 self.task.instrument.writeCuratedCalibrations(butler3) 

146 super().ingest() 

147 

148 # Class attributes that will be shadowed by public instance attributes; 

149 # defined here only for documentation purposes. 

150 

151 mapper: CameraMapper 

152 """Gen2 mapper associated with this repository. 

153 """